feat: course outline page (#694)
* feat: Course outline Top level page (#36) * feat: [2u-259] add components * feat: [2u-259] fix sidebar * feat: [2u-259] add tests, fix links * feat: [2u-259] fix messages * feat: [2u-159] fix reducer and sidebar * feat: [2u-259] fix reducer * feat: [2u-259] remove warning from selectors * feat: [2u-259] remove indents --------- Co-authored-by: Vladislav Keblysh <vladislavkeblysh@Vladislavs-MacBook-Pro.local> feat: Course outline Status Bar (#50) * feat: [2u-259] add components * feat: [2u-259] fix sidebar * feat: [2u-259] add tests, fix links * feat: [2u-259] fix messages * feat: [2u-159] fix reducer and sidebar * feat: add checklist * feat: [2u-259] fix reducer * feat: [2u-259] remove warning from selectors * feat: [2u-259] remove indents * feat: [2u-259] add api, enable modal * feat: [2u-259] add tests * feat: [2u-259] add translates * feat: [2u-271] fix transalates * feat: [2u-281] fix isQuery pending, utils, hooks * feat: [2u-281] fix useScrollToHashElement * feat: [2u-271] fix imports --------- Co-authored-by: Vladislav Keblysh <vladislavkeblysh@Vladislavs-MacBook-Pro.local> feat: Course Outline Reindex (#55) * feat: [2u-277] add alerts * feat: [2u-277] add translates * feat: [2u-277] fix tests * fix: [2u-277] fix slice and hook --------- Co-authored-by: Vladislav Keblysh <vladislavkeblysh@Vladislavs-MacBook-Pro.local> fix: Course outline tests (#56) * fix: fixed course outline status bar tests * fix: fixed course outline status bar tests * fix: fixed course outline enable highlights modal tests * fix: enable modal tests fix: increase code coverage on the page * refactor: improve course outline page feat: lms live link chore: update outline link fix: course outline link refactor: remove unnecessary css and rename test file refactor: remove unnecessary css from outlineSidebar test: make use of message variable instead of hardcoded text refactor: remove unnecessary h5 class test: use test id for detecting component refactor: update course outline url and some default messages --------- Co-authored-by: vladislavkeblysh <138868841+vladislavkeblysh@users.noreply.github.com>
This commit is contained in:
@@ -33,7 +33,6 @@ ENABLE_ACCESSIBILITY_PAGE=false
|
||||
ENABLE_PROGRESS_GRAPH_SETTINGS=false
|
||||
ENABLE_TEAM_TYPE_SETTING=false
|
||||
ENABLE_NEW_EDITOR_PAGES=true
|
||||
ENABLE_NEW_COURSE_OUTLINE_PAGE = false
|
||||
ENABLE_NEW_VIDEO_UPLOAD_PAGE = false
|
||||
ENABLE_UNIT_PAGE = false
|
||||
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = false
|
||||
|
||||
@@ -11,6 +11,7 @@ import VideoSelectorContainer from './selectors/VideoSelectorContainer';
|
||||
import CustomPages from './custom-pages';
|
||||
import { FilesPage, VideosPage } from './files-and-videos';
|
||||
import { AdvancedSettings } from './advanced-settings';
|
||||
import { CourseOutline } from './course-outline';
|
||||
import ScheduleAndDetails from './schedule-and-details';
|
||||
import { GradingSettings } from './grading-settings';
|
||||
import CourseTeam from './course-team/CourseTeam';
|
||||
@@ -41,8 +42,8 @@ const CourseAuthoringRoutes = () => {
|
||||
<CourseAuthoringPage courseId={courseId}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="outline"
|
||||
element={process.env.ENABLE_NEW_COURSE_OUTLINE_PAGE === 'true' ? <PageWrap><Placeholder /></PageWrap> : null}
|
||||
path="/"
|
||||
element={<PageWrap><CourseOutline courseId={courseId} /></PageWrap>}
|
||||
/>
|
||||
<Route
|
||||
path="course_info"
|
||||
|
||||
141
src/course-outline/CourseOutline.jsx
Normal file
141
src/course-outline/CourseOutline.jsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Container,
|
||||
Layout,
|
||||
TransitionReplace,
|
||||
} from '@edx/paragon';
|
||||
import {
|
||||
CheckCircle as CheckCircleIcon,
|
||||
Warning as WarningIcon,
|
||||
} from '@edx/paragon/icons';
|
||||
|
||||
import SubHeader from '../generic/sub-header/SubHeader';
|
||||
import { RequestStatus } from '../data/constants';
|
||||
import InternetConnectionAlert from '../generic/internet-connection-alert';
|
||||
import AlertMessage from '../generic/alert-message';
|
||||
import HeaderNavigations from './header-navigations/HeaderNavigations';
|
||||
import OutlineSideBar from './outline-sidebar/OutlineSidebar';
|
||||
import messages from './messages';
|
||||
import { useCourseOutline } from './hooks';
|
||||
import StatusBar from './status-bar/StatusBar';
|
||||
import EnableHighlightsModal from './enable-highlights-modal/EnableHighlightsModal';
|
||||
|
||||
const CourseOutline = ({ courseId }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const {
|
||||
savingStatus,
|
||||
statusBarData,
|
||||
isLoading,
|
||||
isReIndexShow,
|
||||
showErrorAlert,
|
||||
showSuccessAlert,
|
||||
isSectionsExpanded,
|
||||
isEnableHighlightsModalOpen,
|
||||
isInternetConnectionAlertFailed,
|
||||
isDisabledReindexButton,
|
||||
headerNavigationsActions,
|
||||
openEnableHighlightsModal,
|
||||
closeEnableHighlightsModal,
|
||||
handleEnableHighlightsSubmit,
|
||||
handleInternetConnectionFailed,
|
||||
} = useCourseOutline({ courseId });
|
||||
|
||||
if (isLoading) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container size="xl" className="px-4">
|
||||
<section className="course-outline-container mb-4 mt-5">
|
||||
<TransitionReplace>
|
||||
{showSuccessAlert ? (
|
||||
<AlertMessage
|
||||
key={intl.formatMessage(messages.alertSuccessAriaLabelledby)}
|
||||
show={showSuccessAlert}
|
||||
variant="success"
|
||||
icon={CheckCircleIcon}
|
||||
title={intl.formatMessage(messages.alertSuccessTitle)}
|
||||
description={intl.formatMessage(messages.alertSuccessDescription)}
|
||||
aria-hidden="true"
|
||||
aria-labelledby={intl.formatMessage(messages.alertSuccessAriaLabelledby)}
|
||||
aria-describedby={intl.formatMessage(messages.alertSuccessAriaDescribedby)}
|
||||
/>
|
||||
) : null}
|
||||
</TransitionReplace>
|
||||
<SubHeader
|
||||
className="mt-5"
|
||||
title={intl.formatMessage(messages.headingTitle)}
|
||||
subtitle={intl.formatMessage(messages.headingSubtitle)}
|
||||
headerActions={(
|
||||
<HeaderNavigations
|
||||
isReIndexShow={isReIndexShow}
|
||||
isSectionsExpanded={isSectionsExpanded}
|
||||
headerNavigationsActions={headerNavigationsActions}
|
||||
isDisabledReindexButton={isDisabledReindexButton}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Layout
|
||||
lg={[{ span: 9 }, { span: 3 }]}
|
||||
md={[{ span: 9 }, { span: 3 }]}
|
||||
sm={[{ span: 12 }, { span: 12 }]}
|
||||
xs={[{ span: 12 }, { span: 12 }]}
|
||||
xl={[{ span: 9 }, { span: 3 }]}
|
||||
>
|
||||
<Layout.Element>
|
||||
<article>
|
||||
<div>
|
||||
<section className="course-outline-section">
|
||||
<StatusBar
|
||||
courseId={courseId}
|
||||
isLoading={isLoading}
|
||||
statusBarData={statusBarData}
|
||||
openEnableHighlightsModal={openEnableHighlightsModal}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
</Layout.Element>
|
||||
<Layout.Element>
|
||||
<OutlineSideBar courseId={courseId} />
|
||||
</Layout.Element>
|
||||
</Layout>
|
||||
<EnableHighlightsModal
|
||||
isOpen={isEnableHighlightsModalOpen}
|
||||
close={closeEnableHighlightsModal}
|
||||
onEnableHighlightsSubmit={handleEnableHighlightsSubmit}
|
||||
highlightsDocUrl={statusBarData.highlightsDocUrl}
|
||||
/>
|
||||
</section>
|
||||
</Container>
|
||||
<div className="alert-toast">
|
||||
<InternetConnectionAlert
|
||||
isFailed={isInternetConnectionAlertFailed}
|
||||
isQueryPending={savingStatus === RequestStatus.PENDING}
|
||||
onInternetConnectionFailed={handleInternetConnectionFailed}
|
||||
/>
|
||||
{showErrorAlert && (
|
||||
<AlertMessage
|
||||
key={intl.formatMessage(messages.alertErrorTitle)}
|
||||
show={showErrorAlert}
|
||||
variant="danger"
|
||||
icon={WarningIcon}
|
||||
title={intl.formatMessage(messages.alertErrorTitle)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CourseOutline.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default CourseOutline;
|
||||
2
src/course-outline/CourseOutline.scss
Normal file
2
src/course-outline/CourseOutline.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
@import "./header-navigations/HeaderNavigations";
|
||||
@import "./status-bar/StatusBar";
|
||||
150
src/course-outline/CourseOutline.test.jsx
Normal file
150
src/course-outline/CourseOutline.test.jsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import React from 'react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
import {
|
||||
getCourseBestPracticesApiUrl,
|
||||
getCourseLaunchApiUrl,
|
||||
getCourseOutlineIndexApiUrl,
|
||||
getCourseReindexApiUrl,
|
||||
getEnableHighlightsEmailsApiUrl,
|
||||
} from './data/api';
|
||||
import {
|
||||
enableCourseHighlightsEmailsQuery,
|
||||
fetchCourseBestPracticesQuery,
|
||||
fetchCourseLaunchQuery,
|
||||
fetchCourseOutlineIndexQuery,
|
||||
fetchCourseReindexQuery,
|
||||
} from './data/thunk';
|
||||
import initializeStore from '../store';
|
||||
import {
|
||||
courseOutlineIndexMock,
|
||||
courseBestPracticesMock,
|
||||
courseLaunchMock,
|
||||
} from './__mocks__';
|
||||
import { executeThunk } from '../utils';
|
||||
import CourseOutline from './CourseOutline';
|
||||
import messages from './messages';
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
const mockPathname = '/foo-bar';
|
||||
const courseId = '123';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: () => ({
|
||||
pathname: mockPathname,
|
||||
}),
|
||||
}));
|
||||
|
||||
const RootWrapper = () => (
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en">
|
||||
<CourseOutline courseId={courseId} />
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
describe('<CourseOutline />', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock
|
||||
.onGet(getCourseOutlineIndexApiUrl(courseId))
|
||||
.reply(200, courseOutlineIndexMock);
|
||||
await executeThunk(fetchCourseOutlineIndexQuery(courseId), store.dispatch);
|
||||
});
|
||||
|
||||
it('render CourseOutline component correctly', async () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('check reindex and render success alert is correctly', async () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseReindexApiUrl(courseOutlineIndexMock.reindexLink))
|
||||
.reply(200);
|
||||
await executeThunk(fetchCourseReindexQuery(courseId, courseOutlineIndexMock.reindexLink), store.dispatch);
|
||||
|
||||
expect(getByText(messages.alertSuccessDescription.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render error alert after failed reindex correctly', async () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseReindexApiUrl('some link'))
|
||||
.reply(500);
|
||||
await executeThunk(fetchCourseReindexQuery(courseId, 'some link'), store.dispatch);
|
||||
|
||||
expect(getByText(messages.alertErrorTitle.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render checklist value correctly', async () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseBestPracticesApiUrl({
|
||||
courseId, excludeGraded: true, all: true,
|
||||
}))
|
||||
.reply(200, courseBestPracticesMock);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseLaunchApiUrl({
|
||||
courseId, gradedOnly: true, validateOras: true, all: true,
|
||||
}))
|
||||
.reply(200, courseLaunchMock);
|
||||
|
||||
await executeThunk(fetchCourseLaunchQuery({
|
||||
courseId, gradedOnly: true, validateOras: true, all: true,
|
||||
}), store.dispatch);
|
||||
await executeThunk(fetchCourseBestPracticesQuery({
|
||||
courseId, excludeGraded: true, all: true,
|
||||
}), store.dispatch);
|
||||
|
||||
expect(getByText('4/9 completed')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('check highlights are enabled after enable highlights query is successful', async () => {
|
||||
const { findByTestId } = render(<RootWrapper />);
|
||||
|
||||
axiosMock
|
||||
.onGet(getCourseOutlineIndexApiUrl(courseId))
|
||||
.reply(200, {
|
||||
...courseOutlineIndexMock,
|
||||
highlightsEnabledForMessaging: false,
|
||||
});
|
||||
|
||||
axiosMock
|
||||
.onPost(getEnableHighlightsEmailsApiUrl(courseId), {
|
||||
publish: 'republish',
|
||||
metadata: {
|
||||
highlights_enabled_for_messaging: true,
|
||||
},
|
||||
})
|
||||
.reply(200);
|
||||
|
||||
await executeThunk(enableCourseHighlightsEmailsQuery(courseId), store.dispatch);
|
||||
expect(await findByTestId('highlights-enabled-span')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
43
src/course-outline/__mocks__/courseBestPractices.js
Normal file
43
src/course-outline/__mocks__/courseBestPractices.js
Normal file
@@ -0,0 +1,43 @@
|
||||
module.exports = {
|
||||
isSelfPaced: false,
|
||||
sections: {
|
||||
totalNumber: 6,
|
||||
totalVisible: 4,
|
||||
numberWithHighlights: 2,
|
||||
highlightsActiveForCourse: true,
|
||||
highlightsEnabled: true,
|
||||
},
|
||||
subsections: {
|
||||
totalVisible: 5,
|
||||
numWithOneBlockType: 2,
|
||||
numBlockTypes: {
|
||||
min: 0,
|
||||
max: 3,
|
||||
mean: 1,
|
||||
median: 1,
|
||||
mode: 1,
|
||||
},
|
||||
},
|
||||
units: {
|
||||
totalVisible: 9,
|
||||
numBlocks: {
|
||||
min: 1,
|
||||
max: 2,
|
||||
mean: 2,
|
||||
median: 2,
|
||||
mode: 2,
|
||||
},
|
||||
},
|
||||
videos: {
|
||||
totalNumber: 7,
|
||||
numMobileEncoded: 0,
|
||||
numWithValId: 3,
|
||||
durations: {
|
||||
min: null,
|
||||
max: null,
|
||||
mean: null,
|
||||
median: null,
|
||||
mode: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
31
src/course-outline/__mocks__/courseLaunch.js
Normal file
31
src/course-outline/__mocks__/courseLaunch.js
Normal file
@@ -0,0 +1,31 @@
|
||||
module.exports = {
|
||||
isSelfPaced: false,
|
||||
dates: {
|
||||
hasStartDate: true,
|
||||
hasEndDate: false,
|
||||
},
|
||||
assignments: {
|
||||
totalNumber: 11,
|
||||
totalVisible: 7,
|
||||
assignmentsWithDatesBeforeStart: [],
|
||||
assignmentsWithDatesAfterEnd: [],
|
||||
assignmentsWithOraDatesBeforeStart: [],
|
||||
assignmentsWithOraDatesAfterEnd: [],
|
||||
},
|
||||
grades: {
|
||||
hasGradingPolicy: true,
|
||||
sumOfWeights: 1,
|
||||
},
|
||||
certificates: {
|
||||
isActivated: false,
|
||||
hasCertificate: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
updates: {
|
||||
hasUpdate: true,
|
||||
},
|
||||
proctoring: {
|
||||
needsProctoringEscalationEmail: false,
|
||||
hasProctoringEscalationEmail: false,
|
||||
},
|
||||
};
|
||||
2952
src/course-outline/__mocks__/courseOutlineIndex.js
Normal file
2952
src/course-outline/__mocks__/courseOutlineIndex.js
Normal file
@@ -0,0 +1,2952 @@
|
||||
module.exports = {
|
||||
courseReleaseDate: 'Set Date',
|
||||
courseStructure: {
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@course+block@course',
|
||||
displayName: 'Demonstration Course',
|
||||
category: 'course',
|
||||
hasChildren: true,
|
||||
unitLevelDiscussions: false,
|
||||
editedOn: 'Aug 23, 2023 at 12:35 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Aug 23, 2023 at 11:32 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course',
|
||||
releasedToStudents: false,
|
||||
releaseDate: 'Nov 09, 2023 at 22:00 UTC',
|
||||
visibilityState: null,
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2023-11-09T22:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
highlightsEnabledForMessaging: true,
|
||||
highlightsEnabled: true,
|
||||
highlightsPreviewOnly: false,
|
||||
highlightsDocUrl: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_sections.html#set-section-highlights-for-weekly-course-highlight-messages',
|
||||
enableProctoredExams: false,
|
||||
createZendeskTickets: true,
|
||||
enableTimedExams: true,
|
||||
childInfo: {
|
||||
category: 'chapter',
|
||||
displayName: 'Section',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@d8a6192ade314473a78242dfeedfbf5b',
|
||||
displayName: 'Introduction 12',
|
||||
category: 'chapter',
|
||||
hasChildren: true,
|
||||
editedOn: 'Aug 23, 2023 at 12:35 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Aug 23, 2023 at 12:35 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40chapter%2Bblock%40d8a6192ade314473a78242dfeedfbf5b',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Aug 10, 2023 at 22:00 UTC',
|
||||
visibilityState: 'staff_only',
|
||||
hasExplicitStaffLock: true,
|
||||
start: '2023-08-10T22:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
highlights: [
|
||||
'New Highlight 1',
|
||||
'New Highlight 4',
|
||||
],
|
||||
highlightsEnabled: true,
|
||||
highlightsPreviewOnly: false,
|
||||
highlightsDocUrl: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_sections.html#set-section-highlights-for-weekly-course-highlight-messages',
|
||||
childInfo: {
|
||||
category: 'sequential',
|
||||
displayName: 'Subsection',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction',
|
||||
displayName: 'Demo Course Overview',
|
||||
category: 'sequential',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40sequential%2Bblock%40edx_introduction',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Jan 01, 1970 at 05:00 UTC',
|
||||
visibilityState: 'staff_only',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '1970-01-01T05:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
hideAfterDue: false,
|
||||
isProctoredExam: false,
|
||||
wasExamEverLinkedWithExternal: false,
|
||||
onlineProctoringRules: '',
|
||||
isPracticeExam: false,
|
||||
isOnboardingExam: false,
|
||||
isTimeLimited: false,
|
||||
examReviewRules: '',
|
||||
defaultTimeLimitMinutes: null,
|
||||
proctoringExamConfigurationLink: null,
|
||||
supportsOnboarding: false,
|
||||
showReviewRules: true,
|
||||
childInfo: {
|
||||
category: 'vertical',
|
||||
displayName: 'Unit',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_0270f6de40fc',
|
||||
displayName: 'Introduction: Video and Sequences',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_0270f6de40fc',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Jan 01, 1970 at 05:00 UTC',
|
||||
visibilityState: 'staff_only',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '1970-01-01T05:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: true,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: true,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: true,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@graded_interactions',
|
||||
displayName: 'Example Week 2: Get Interactive',
|
||||
category: 'chapter',
|
||||
hasChildren: true,
|
||||
editedOn: 'Aug 16, 2023 at 11:52 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Aug 16, 2023 at 11:52 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40chapter%2Bblock%40graded_interactions',
|
||||
releasedToStudents: false,
|
||||
releaseDate: 'Nov 09, 2023 at 22:00 UTC',
|
||||
visibilityState: 'ready',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2023-11-09T22:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
highlights: [
|
||||
'New',
|
||||
],
|
||||
highlightsEnabled: true,
|
||||
highlightsPreviewOnly: false,
|
||||
highlightsDocUrl: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_sections.html#set-section-highlights-for-weekly-course-highlight-messages',
|
||||
childInfo: {
|
||||
category: 'sequential',
|
||||
displayName: 'Subsection',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@simulations',
|
||||
displayName: "Lesson 2 - Let's Get Interactive!",
|
||||
category: 'sequential',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40sequential%2Bblock%40simulations',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Jan 01, 1970 at 05:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '1970-01-01T05:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
hideAfterDue: false,
|
||||
isProctoredExam: false,
|
||||
wasExamEverLinkedWithExternal: false,
|
||||
onlineProctoringRules: '',
|
||||
isPracticeExam: false,
|
||||
isOnboardingExam: false,
|
||||
isTimeLimited: false,
|
||||
examReviewRules: '',
|
||||
defaultTimeLimitMinutes: null,
|
||||
proctoringExamConfigurationLink: null,
|
||||
supportsOnboarding: false,
|
||||
showReviewRules: true,
|
||||
childInfo: {
|
||||
category: 'vertical',
|
||||
displayName: 'Unit',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@d0d804e8863c4a95a659c04d8a2b2bc0',
|
||||
displayName: "Lesson 2 - Let's Get Interactive! ",
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@d0d804e8863c4a95a659c04d8a2b2bc0',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Jan 01, 1970 at 05:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '1970-01-01T05:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_2dbb0072785e',
|
||||
displayName: 'An Interactive Reference Table',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_2dbb0072785e',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Jan 01, 1970 at 05:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '1970-01-01T05:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_98cf62510471',
|
||||
displayName: 'Zooming Diagrams',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_98cf62510471',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Jan 01, 1970 at 05:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '1970-01-01T05:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_d32bf9b2242c',
|
||||
displayName: 'Electronic Sound Experiment',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_d32bf9b2242c',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Jan 01, 1970 at 05:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '1970-01-01T05:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@4e592689563243c484af947465eaef0d',
|
||||
displayName: 'New Unit',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@4e592689563243c484af947465eaef0d',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Jan 01, 1970 at 05:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '1970-01-01T05:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@graded_simulations',
|
||||
displayName: 'Homework - Labs and Demos',
|
||||
category: 'sequential',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40sequential%2Bblock%40graded_simulations',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: 'Homework',
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
hideAfterDue: false,
|
||||
isProctoredExam: false,
|
||||
wasExamEverLinkedWithExternal: false,
|
||||
onlineProctoringRules: '',
|
||||
isPracticeExam: false,
|
||||
isOnboardingExam: false,
|
||||
isTimeLimited: false,
|
||||
examReviewRules: '',
|
||||
defaultTimeLimitMinutes: null,
|
||||
proctoringExamConfigurationLink: null,
|
||||
supportsOnboarding: false,
|
||||
showReviewRules: true,
|
||||
childInfo: {
|
||||
category: 'vertical',
|
||||
displayName: 'Unit',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@d6cee45205a449369d7ef8f159b22bdf',
|
||||
displayName: 'Labs and Demos',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@d6cee45205a449369d7ef8f159b22bdf',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_aae927868e55',
|
||||
displayName: 'Code Grader',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_aae927868e55',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_c037f3757df1',
|
||||
displayName: 'Electric Circuit Simulator',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_c037f3757df1',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_bc69a47c6fae',
|
||||
displayName: 'Protein Creator',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_bc69a47c6fae',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@8f89194410954e768bde1764985454a7',
|
||||
displayName: 'Molecule Structures',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@8f89194410954e768bde1764985454a7',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@175e76c4951144a29d46211361266e0e',
|
||||
displayName: 'Homework - Essays',
|
||||
category: 'sequential',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40sequential%2Bblock%40175e76c4951144a29d46211361266e0e',
|
||||
releasedToStudents: false,
|
||||
releaseDate: 'Nov 09, 2023 at 22:00 UTC',
|
||||
visibilityState: 'ready',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2023-11-09T22:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
hideAfterDue: false,
|
||||
isProctoredExam: false,
|
||||
wasExamEverLinkedWithExternal: false,
|
||||
onlineProctoringRules: '',
|
||||
isPracticeExam: false,
|
||||
isOnboardingExam: false,
|
||||
isTimeLimited: false,
|
||||
examReviewRules: '',
|
||||
defaultTimeLimitMinutes: null,
|
||||
proctoringExamConfigurationLink: null,
|
||||
supportsOnboarding: false,
|
||||
showReviewRules: true,
|
||||
childInfo: {
|
||||
category: 'vertical',
|
||||
displayName: 'Unit',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@fb79dcbad35b466a8c6364f8ffee9050',
|
||||
displayName: 'Peer Assessed Essays',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@fb79dcbad35b466a8c6364f8ffee9050',
|
||||
releasedToStudents: false,
|
||||
releaseDate: 'Nov 09, 2023 at 22:00 UTC',
|
||||
visibilityState: 'ready',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2023-11-09T22:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@1414ffd5143b4b508f739b563ab468b7',
|
||||
displayName: 'About Exams and Certificates',
|
||||
category: 'chapter',
|
||||
hasChildren: true,
|
||||
editedOn: 'Aug 10, 2023 at 10:40 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Aug 10, 2023 at 10:40 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40chapter%2Bblock%401414ffd5143b4b508f739b563ab468b7',
|
||||
releasedToStudents: false,
|
||||
releaseDate: 'Jan 01, 2030 at 05:00 UTC',
|
||||
visibilityState: 'needs_attention',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2030-01-01T05:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
highlights: [],
|
||||
highlightsEnabled: true,
|
||||
highlightsPreviewOnly: false,
|
||||
highlightsDocUrl: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_sections.html#set-section-highlights-for-weekly-course-highlight-messages',
|
||||
childInfo: {
|
||||
category: 'sequential',
|
||||
displayName: 'Subsection',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow',
|
||||
displayName: 'edX Exams',
|
||||
category: 'sequential',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40sequential%2Bblock%40workflow',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: 'Exam',
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
hideAfterDue: false,
|
||||
isProctoredExam: false,
|
||||
wasExamEverLinkedWithExternal: false,
|
||||
onlineProctoringRules: '',
|
||||
isPracticeExam: false,
|
||||
isOnboardingExam: false,
|
||||
isTimeLimited: false,
|
||||
examReviewRules: '',
|
||||
defaultTimeLimitMinutes: null,
|
||||
proctoringExamConfigurationLink: null,
|
||||
supportsOnboarding: false,
|
||||
showReviewRules: true,
|
||||
childInfo: {
|
||||
category: 'vertical',
|
||||
displayName: 'Unit',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@934cc32c177d41b580c8413e561346b3',
|
||||
displayName: 'EdX Exams',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@934cc32c177d41b580c8413e561346b3',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_f04afeac0131',
|
||||
displayName: 'Immediate Feedback',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_f04afeac0131',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@b6662b497c094bcc9b870d8270c90c93',
|
||||
displayName: 'Getting Answers',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@b6662b497c094bcc9b870d8270c90c93',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@f91d8d31f7cf48ce990f8d8745ae4cfa',
|
||||
displayName: 'Answering More Than Once',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@f91d8d31f7cf48ce990f8d8745ae4cfa',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_ac391cde8a91',
|
||||
displayName: 'Limited Checks',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_ac391cde8a91',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_36e0beb03f0a',
|
||||
displayName: 'Randomized Questions',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_36e0beb03f0a',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@1b0e2c2c84884b95b1c99fb678cc964c',
|
||||
displayName: 'Overall Grade Performance',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@1b0e2c2c84884b95b1c99fb678cc964c',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@c7e98fd39a6944edb6b286c32e1150ff',
|
||||
displayName: 'Passing a Course',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@c7e98fd39a6944edb6b286c32e1150ff',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@d6eaa391d2be41dea20b8b1bfbcb1c45',
|
||||
displayName: 'Getting Your edX Certificate',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Jul 07, 2023 at 11:14 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@d6eaa391d2be41dea20b8b1bfbcb1c45',
|
||||
releasedToStudents: true,
|
||||
releaseDate: 'Feb 05, 2013 at 00:00 UTC',
|
||||
visibilityState: 'live',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2013-02-05T00:00:00Z',
|
||||
graded: true,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@46e11a7b395f45b9837df6c6ac609004',
|
||||
displayName: 'Publish section',
|
||||
category: 'chapter',
|
||||
hasChildren: true,
|
||||
editedOn: 'Aug 23, 2023 at 12:22 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Aug 23, 2023 at 12:22 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40chapter%2Bblock%4046e11a7b395f45b9837df6c6ac609004',
|
||||
releasedToStudents: false,
|
||||
releaseDate: 'Nov 09, 2023 at 22:00 UTC',
|
||||
visibilityState: 'ready',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2023-11-09T22:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
highlights: [],
|
||||
highlightsEnabled: true,
|
||||
highlightsPreviewOnly: false,
|
||||
highlightsDocUrl: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_sections.html#set-section-highlights-for-weekly-course-highlight-messages',
|
||||
childInfo: {
|
||||
category: 'sequential',
|
||||
displayName: 'Subsection',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@1945e9656cbe4abe8f2020c67e9e1f61',
|
||||
displayName: 'Subsection sub',
|
||||
category: 'sequential',
|
||||
hasChildren: true,
|
||||
editedOn: 'Aug 23, 2023 at 11:32 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Aug 23, 2023 at 11:33 UTC',
|
||||
studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40sequential%2Bblock%401945e9656cbe4abe8f2020c67e9e1f61',
|
||||
releasedToStudents: false,
|
||||
releaseDate: 'Nov 09, 2023 at 22:00 UTC',
|
||||
visibilityState: 'ready',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2023-11-09T22:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
hideAfterDue: false,
|
||||
isProctoredExam: false,
|
||||
wasExamEverLinkedWithExternal: false,
|
||||
onlineProctoringRules: '',
|
||||
isPracticeExam: false,
|
||||
isOnboardingExam: false,
|
||||
isTimeLimited: false,
|
||||
examReviewRules: '',
|
||||
defaultTimeLimitMinutes: null,
|
||||
proctoringExamConfigurationLink: null,
|
||||
supportsOnboarding: false,
|
||||
showReviewRules: true,
|
||||
childInfo: {
|
||||
category: 'vertical',
|
||||
displayName: 'Unit',
|
||||
children: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@b8149aa5af944aed8eebf9c7dc9f3d0b',
|
||||
displayName: 'Unit',
|
||||
category: 'vertical',
|
||||
hasChildren: true,
|
||||
editedOn: 'Aug 23, 2023 at 11:32 UTC',
|
||||
published: true,
|
||||
publishedOn: 'Aug 23, 2023 at 11:33 UTC',
|
||||
studioUrl: '/container/block-v1:edX+DemoX+Demo_Course+type@vertical+block@b8149aa5af944aed8eebf9c7dc9f3d0b',
|
||||
releasedToStudents: false,
|
||||
releaseDate: 'Nov 09, 2023 at 22:00 UTC',
|
||||
visibilityState: 'ready',
|
||||
hasExplicitStaffLock: false,
|
||||
start: '2023-11-09T22:00:00Z',
|
||||
graded: false,
|
||||
dueDate: '',
|
||||
due: null,
|
||||
relativeWeeksDue: null,
|
||||
format: null,
|
||||
courseGraders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
hasChanges: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatoryMessage: null,
|
||||
groupAccess: {},
|
||||
userPartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
showCorrectness: 'always',
|
||||
discussionEnabled: true,
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
staffOnlyMessage: false,
|
||||
hasPartitionGroupComponents: false,
|
||||
userPartitionInfo: {
|
||||
selectablePartitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selectedPartitionIndex: -1,
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
deprecatedBlocksInfo: {
|
||||
deprecatedEnabledBlockTypes: [],
|
||||
blocks: [],
|
||||
advanceSettingsUrl: '/settings/advanced/course-v1:edx+101+y76',
|
||||
},
|
||||
discussionsIncontextFeedbackUrl: '',
|
||||
discussionsIncontextLearnmoreUrl: '',
|
||||
initialState: {
|
||||
expandedLocators: [
|
||||
'block-v1:edx+101+y76+type@chapter+block@03de0adc9d1c4cc097062d80eb04abf6',
|
||||
'block-v1:edx+101+y76+type@sequential+block@8a85e287e30a47e98d8c1f37f74a6a9d',
|
||||
],
|
||||
locatorToShow: 'block-v1:edx+101+y76+type@chapter+block@03de0adc9d1c4cc097062d80eb04abf6',
|
||||
},
|
||||
languageCode: 'en',
|
||||
lmsLink: '//localhost:18000/courses/course-v1:edx+101+y76/jump_to/block-v1:edx+101+y76+type@course+block@course',
|
||||
mfeProctoredExamSettingsUrl: '',
|
||||
notificationDismissUrl: '/course_notifications/course-v1:edx+101+y76/2',
|
||||
proctoringErrors: [],
|
||||
reindexLink: '/course/course-v1:edx+101+y76/search_reindex',
|
||||
rerunNotificationId: 2,
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
courseReleaseDate: 'Set Date',
|
||||
courseStructure: {},
|
||||
deprecatedBlocksInfo: {
|
||||
deprecatedEnabledBlockTypes: [],
|
||||
blocks: [],
|
||||
advanceSettingsUrl: '/settings/advanced/course-v1:edx+101+y76',
|
||||
},
|
||||
discussionsIncontextFeedbackUrl: '',
|
||||
discussionsIncontextLearnmoreUrl: '',
|
||||
initialState: {
|
||||
expandedLocators: [
|
||||
'block-v1:edx+101+y76+type@chapter+block@03de0adc9d1c4cc097062d80eb04abf6',
|
||||
'block-v1:edx+101+y76+type@sequential+block@8a85e287e30a47e98d8c1f37f74a6a9d',
|
||||
],
|
||||
locatorToShow: 'block-v1:edx+101+y76+type@chapter+block@03de0adc9d1c4cc097062d80eb04abf6',
|
||||
},
|
||||
languageCode: 'en',
|
||||
lmsLink: '//localhost:18000/courses/course-v1:edx+101+y76/jump_to/block-v1:edx+101+y76+type@course+block@course',
|
||||
mfeProctoredExamSettingsUrl: '',
|
||||
notificationDismissUrl: '/course_notifications/course-v1:edx+101+y76/2',
|
||||
proctoringErrors: [],
|
||||
reindexLink: '/course/course-v1:edx+101+y76/search_reindex',
|
||||
rerunNotificationId: 2,
|
||||
};
|
||||
4
src/course-outline/__mocks__/index.js
Normal file
4
src/course-outline/__mocks__/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as courseOutlineIndexMock } from './courseOutlineIndex';
|
||||
export { default as courseOutlineIndexWithoutSections } from './courseOutlineIndexWithoutSections';
|
||||
export { default as courseBestPracticesMock } from './courseBestPractices';
|
||||
export { default as courseLaunchMock } from './courseLaunch';
|
||||
59
src/course-outline/constants.js
Normal file
59
src/course-outline/constants.js
Normal file
@@ -0,0 +1,59 @@
|
||||
export const CHECKLIST_FILTERS = {
|
||||
ALL: 'ALL',
|
||||
SELF_PACED: 'SELF_PACED',
|
||||
INSTRUCTOR_PACED: 'INSTRUCTOR_PACED',
|
||||
};
|
||||
|
||||
export const LAUNCH_CHECKLIST = {
|
||||
data: [
|
||||
{
|
||||
id: 'welcomeMessage',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'gradingPolicy',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'certificate',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'courseDates',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'assignmentDeadlines',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.INSTRUCTOR_PACED,
|
||||
},
|
||||
{
|
||||
id: 'proctoringEmail',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const BEST_PRACTICES_CHECKLIST = {
|
||||
data: [
|
||||
{
|
||||
id: 'videoDuration',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'mobileFriendlyVideo',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'diverseSequences',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'weeklyHighlights',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.SELF_PACED,
|
||||
},
|
||||
{
|
||||
id: 'unitDepth',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
],
|
||||
};
|
||||
107
src/course-outline/data/api.js
Normal file
107
src/course-outline/data/api.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
|
||||
|
||||
export const getCourseOutlineIndexApiUrl = (courseId) => `${getApiBaseUrl()}/api/contentstore/v1/course_index/${courseId}`;
|
||||
|
||||
export const getCourseBestPracticesApiUrl = ({
|
||||
courseId,
|
||||
excludeGraded,
|
||||
all,
|
||||
}) => `${getApiBaseUrl()}/api/courses/v1/quality/${courseId}/?exclude_graded=${excludeGraded}&all=${all}`;
|
||||
|
||||
export const getCourseLaunchApiUrl = ({
|
||||
courseId,
|
||||
gradedOnly,
|
||||
validateOras,
|
||||
all,
|
||||
}) => `${getApiBaseUrl()}/api/courses/v1/validation/${courseId}/?graded_only=${gradedOnly}&validate_oras=${validateOras}&all=${all}`;
|
||||
|
||||
export const getEnableHighlightsEmailsApiUrl = (courseId) => {
|
||||
const formattedCourseId = courseId.split('course-v1:')[1];
|
||||
return `${getApiBaseUrl()}/xblock/block-v1:${formattedCourseId}+type@course+block@course`;
|
||||
};
|
||||
|
||||
export const getCourseReindexApiUrl = (reindexLink) => `${getApiBaseUrl()}${reindexLink}`;
|
||||
|
||||
/**
|
||||
* Get course outline index.
|
||||
* @param {string} courseId
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getCourseOutlineIndex(courseId) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getCourseOutlineIndexApiUrl(courseId));
|
||||
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get course best practices.
|
||||
* @param {string} courseId
|
||||
* @param {boolean} excludeGraded
|
||||
* @param {boolean} all
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getCourseBestPractices({
|
||||
courseId,
|
||||
excludeGraded,
|
||||
all,
|
||||
}) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getCourseBestPracticesApiUrl({ courseId, excludeGraded, all }));
|
||||
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get course launch.
|
||||
* @param {string} courseId
|
||||
* @param {boolean} gradedOnly
|
||||
* @param {boolean} validateOras
|
||||
* @param {boolean} all
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function getCourseLaunch({
|
||||
courseId,
|
||||
gradedOnly,
|
||||
validateOras,
|
||||
all,
|
||||
}) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getCourseLaunchApiUrl({
|
||||
courseId, gradedOnly, validateOras, all,
|
||||
}));
|
||||
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable course highlights emails
|
||||
* @param {string} courseId
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function enableCourseHighlightsEmails(courseId) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(getEnableHighlightsEmailsApiUrl(courseId), {
|
||||
publish: 'republish',
|
||||
metadata: {
|
||||
highlights_enabled_for_messaging: true,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart reindex course
|
||||
* @param {string} reindexLink
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function restartIndexingOnCourse(reindexLink) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getCourseReindexApiUrl(reindexLink));
|
||||
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
4
src/course-outline/data/selectors.js
Normal file
4
src/course-outline/data/selectors.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export const getOutlineIndexData = (state) => state.courseOutline.outlineIndexData;
|
||||
export const getLoadingStatus = (state) => state.courseOutline.loadingStatus;
|
||||
export const getStatusBarData = (state) => state.courseOutline.statusBarData;
|
||||
export const getSavingStatus = (state) => state.courseOutline.savingStatus;
|
||||
77
src/course-outline/data/slice.js
Normal file
77
src/course-outline/data/slice.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'courseOutline',
|
||||
initialState: {
|
||||
loadingStatus: {
|
||||
outlineIndexLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
reIndexLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
},
|
||||
outlineIndexData: {},
|
||||
savingStatus: '',
|
||||
statusBarData: {
|
||||
courseReleaseDate: '',
|
||||
highlightsEnabledForMessaging: false,
|
||||
highlightsDocUrl: '',
|
||||
isSelfPaced: false,
|
||||
checklist: {
|
||||
totalCourseLaunchChecks: 0,
|
||||
completedCourseLaunchChecks: 0,
|
||||
totalCourseBestPracticesChecks: 0,
|
||||
completedCourseBestPracticesChecks: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
fetchOutlineIndexSuccess: (state, { payload }) => {
|
||||
state.outlineIndexData = payload;
|
||||
},
|
||||
updateOutlineIndexLoadingStatus: (state, { payload }) => {
|
||||
state.loadingStatus = {
|
||||
...state.loadingStatus,
|
||||
outlineIndexLoadingStatus: payload.status,
|
||||
};
|
||||
},
|
||||
updateReindexLoadingStatus: (state, { payload }) => {
|
||||
state.loadingStatus = {
|
||||
...state.loadingStatus,
|
||||
reIndexLoadingStatus: payload.status,
|
||||
};
|
||||
},
|
||||
updateStatusBar: (state, { payload }) => {
|
||||
state.statusBarData = {
|
||||
...state.statusBarData,
|
||||
...payload,
|
||||
};
|
||||
},
|
||||
fetchStatusBarChecklistSuccess: (state, { payload }) => {
|
||||
state.statusBarData.checklist = {
|
||||
...state.statusBarData.checklist,
|
||||
...payload,
|
||||
};
|
||||
},
|
||||
fetchStatusBarSelPacedSuccess: (state, { payload }) => {
|
||||
state.statusBarData.isSelfPaced = payload.isSelfPaced;
|
||||
},
|
||||
updateSavingStatus: (state, { payload }) => {
|
||||
state.savingStatus = payload.status;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
fetchOutlineIndexSuccess,
|
||||
updateOutlineIndexLoadingStatus,
|
||||
updateReindexLoadingStatus,
|
||||
updateStatusBar,
|
||||
fetchStatusBarChecklistSuccess,
|
||||
fetchStatusBarSelPacedSuccess,
|
||||
updateSavingStatus,
|
||||
} = slice.actions;
|
||||
|
||||
export const {
|
||||
reducer,
|
||||
} = slice;
|
||||
104
src/course-outline/data/thunk.js
Normal file
104
src/course-outline/data/thunk.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import {
|
||||
getCourseBestPracticesChecklist,
|
||||
getCourseLaunchChecklist,
|
||||
} from '../utils/getChecklistForStatusBar';
|
||||
import {
|
||||
enableCourseHighlightsEmails,
|
||||
getCourseBestPractices,
|
||||
getCourseLaunch,
|
||||
getCourseOutlineIndex,
|
||||
restartIndexingOnCourse,
|
||||
} from './api';
|
||||
import {
|
||||
fetchOutlineIndexSuccess,
|
||||
updateOutlineIndexLoadingStatus,
|
||||
updateReindexLoadingStatus,
|
||||
updateStatusBar,
|
||||
fetchStatusBarChecklistSuccess,
|
||||
fetchStatusBarSelPacedSuccess,
|
||||
updateSavingStatus,
|
||||
} from './slice';
|
||||
|
||||
export function fetchCourseOutlineIndexQuery(courseId) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateOutlineIndexLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
|
||||
|
||||
try {
|
||||
const outlineIndex = await getCourseOutlineIndex(courseId);
|
||||
const { courseReleaseDate, courseStructure: { highlightsEnabledForMessaging, highlightsDocUrl } } = outlineIndex;
|
||||
dispatch(fetchOutlineIndexSuccess(outlineIndex));
|
||||
dispatch(updateStatusBar({ courseReleaseDate, highlightsEnabledForMessaging, highlightsDocUrl }));
|
||||
|
||||
dispatch(updateOutlineIndexLoadingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
} catch (error) {
|
||||
dispatch(updateOutlineIndexLoadingStatus({ status: RequestStatus.FAILED }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchCourseLaunchQuery({
|
||||
courseId,
|
||||
gradedOnly = true,
|
||||
validateOras = true,
|
||||
all = true,
|
||||
}) {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
const data = await getCourseLaunch({
|
||||
courseId, gradedOnly, validateOras, all,
|
||||
});
|
||||
dispatch(fetchStatusBarSelPacedSuccess({ isSelfPaced: data.isSelfPaced }));
|
||||
dispatch(fetchStatusBarChecklistSuccess(getCourseLaunchChecklist(data)));
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchCourseBestPracticesQuery({
|
||||
courseId,
|
||||
excludeGraded = true,
|
||||
all = true,
|
||||
}) {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
const data = await getCourseBestPractices({ courseId, excludeGraded, all });
|
||||
dispatch(fetchStatusBarChecklistSuccess(getCourseBestPracticesChecklist(data)));
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function enableCourseHighlightsEmailsQuery(courseId) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
|
||||
|
||||
try {
|
||||
await enableCourseHighlightsEmails(courseId);
|
||||
dispatch(fetchCourseOutlineIndexQuery(courseId));
|
||||
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
} catch (error) {
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchCourseReindexQuery(courseId, reindexLink) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateReindexLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
|
||||
|
||||
try {
|
||||
await restartIndexingOnCourse(reindexLink);
|
||||
dispatch(updateReindexLoadingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
} catch (error) {
|
||||
dispatch(updateReindexLoadingStatus({ status: RequestStatus.FAILED }));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
ActionRow, AlertModal, Button, Hyperlink,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const EnableHighlightsModal = ({
|
||||
onEnableHighlightsSubmit,
|
||||
isOpen,
|
||||
close,
|
||||
highlightsDocUrl,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<AlertModal
|
||||
title={intl.formatMessage(messages.title)}
|
||||
variant="default"
|
||||
size="lg"
|
||||
isOpen={isOpen}
|
||||
onClose={close}
|
||||
footerNode={(
|
||||
<ActionRow>
|
||||
<Button variant="tertiary" onClick={close}>
|
||||
{intl.formatMessage(messages.cancelButton)}
|
||||
</Button>
|
||||
<Button onClick={onEnableHighlightsSubmit}>
|
||||
{intl.formatMessage(messages.submitButton)}
|
||||
</Button>
|
||||
</ActionRow>
|
||||
)}
|
||||
>
|
||||
<p className="small">{intl.formatMessage(messages.description_1)}</p>
|
||||
<p className="small">
|
||||
{intl.formatMessage(messages.description_2)}
|
||||
<Hyperlink
|
||||
className="small ml-2 text-decoration-none"
|
||||
destination={highlightsDocUrl}
|
||||
target="_blank"
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
{intl.formatMessage(messages.link)}
|
||||
</Hyperlink>
|
||||
</p>
|
||||
</AlertModal>
|
||||
);
|
||||
};
|
||||
|
||||
EnableHighlightsModal.propTypes = {
|
||||
onEnableHighlightsSubmit: PropTypes.func.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
close: PropTypes.func.isRequired,
|
||||
highlightsDocUrl: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default EnableHighlightsModal;
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import EnableHighlightsModal from './EnableHighlightsModal';
|
||||
import messages from './messages';
|
||||
|
||||
const onEnableHighlightsSubmitMock = jest.fn();
|
||||
const closeMock = jest.fn();
|
||||
|
||||
const highlightsDocUrl = 'https://example.com/';
|
||||
|
||||
const renderComponent = (props) => render(
|
||||
<IntlProvider locale="en">
|
||||
<EnableHighlightsModal
|
||||
isOpen
|
||||
close={closeMock}
|
||||
onEnableHighlightsSubmit={onEnableHighlightsSubmitMock}
|
||||
highlightsDocUrl={highlightsDocUrl}
|
||||
{...props}
|
||||
/>
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
describe('<EnableHighlightsModal />', () => {
|
||||
it('renders EnableHighlightsModal component correctly', () => {
|
||||
const { getByText, getByRole } = renderComponent();
|
||||
|
||||
expect(getByText(messages.title.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.description_1.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.description_2.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.cancelButton.defaultMessage })).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.submitButton.defaultMessage })).toBeInTheDocument();
|
||||
|
||||
const hyperlink = getByText(messages.link.defaultMessage);
|
||||
expect(hyperlink).toBeInTheDocument();
|
||||
expect(hyperlink.href).toBe(highlightsDocUrl);
|
||||
});
|
||||
|
||||
it('calls onEnableHighlightsSubmit function when the "Submit" button is clicked', () => {
|
||||
const { getByRole } = renderComponent();
|
||||
|
||||
const submitButton = getByRole('button', { name: messages.submitButton.defaultMessage });
|
||||
fireEvent.click(submitButton);
|
||||
expect(onEnableHighlightsSubmitMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the close function when the "Cancel" button is clicked', () => {
|
||||
const { getByRole } = renderComponent();
|
||||
|
||||
const cancelButton = getByRole('button', { name: messages.cancelButton.defaultMessage });
|
||||
fireEvent.click(cancelButton);
|
||||
expect(closeMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
30
src/course-outline/enable-highlights-modal/messages.js
Normal file
30
src/course-outline/enable-highlights-modal/messages.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'course-authoring.course-outline.status-bar.modal.title',
|
||||
defaultMessage: 'Enable course highlight emails',
|
||||
},
|
||||
description_1: {
|
||||
id: 'course-authoring.course-outline.status-bar.modal.description-1',
|
||||
defaultMessage: 'When you enable course highlight emails, learners automatically receive email messages for each section that has highlights. You cannot disable highlights after you start sending them.',
|
||||
},
|
||||
description_2: {
|
||||
id: 'course-authoring.course-outline.status-bar.modal.description-2',
|
||||
defaultMessage: 'Are you sure you want to enable course highlight emails?',
|
||||
},
|
||||
link: {
|
||||
id: 'course-authoring.course-outline.status-bar.modal.link',
|
||||
defaultMessage: 'Learn more',
|
||||
},
|
||||
cancelButton: {
|
||||
id: 'course-authoring.course-outline.status-bar.modal.cancelButton',
|
||||
defaultMessage: 'Cancel',
|
||||
},
|
||||
submitButton: {
|
||||
id: 'course-authoring.course-outline.status-bar.modal.submitButton',
|
||||
defaultMessage: 'Enable',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
100
src/course-outline/header-navigations/HeaderNavigations.jsx
Normal file
100
src/course-outline/header-navigations/HeaderNavigations.jsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button, OverlayTrigger, Tooltip } from '@edx/paragon';
|
||||
import {
|
||||
Add as IconAdd,
|
||||
ArrowDropDown as ArrowDownIcon,
|
||||
ArrowDropUp as ArrowUpIcon,
|
||||
} from '@edx/paragon/icons';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const HeaderNavigations = ({
|
||||
headerNavigationsActions,
|
||||
isReIndexShow,
|
||||
isSectionsExpanded,
|
||||
isDisabledReindexButton,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
handleNewSection, handleReIndex, handleExpandAll, lmsLink,
|
||||
} = headerNavigationsActions;
|
||||
|
||||
return (
|
||||
<nav className="header-navigations ml-auto">
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={(
|
||||
<Tooltip id={intl.formatMessage(messages.newSectionButtonTooltip)}>
|
||||
{intl.formatMessage(messages.newSectionButtonTooltip)}
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
iconBefore={IconAdd}
|
||||
onClick={handleNewSection}
|
||||
>
|
||||
{intl.formatMessage(messages.newSectionButton)}
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
{isReIndexShow && (
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={!isDisabledReindexButton ? (
|
||||
<Tooltip id={intl.formatMessage(messages.reindexButtonTooltip)}>
|
||||
{intl.formatMessage(messages.reindexButtonTooltip)}
|
||||
</Tooltip>
|
||||
) : <React.Fragment key="reindex close" />}
|
||||
>
|
||||
<Button
|
||||
onClick={handleReIndex}
|
||||
variant="outline-primary"
|
||||
disabled={isDisabledReindexButton}
|
||||
>
|
||||
{intl.formatMessage(messages.reindexButton)}
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
)}
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
iconBefore={isSectionsExpanded ? ArrowUpIcon : ArrowDownIcon}
|
||||
onClick={handleExpandAll}
|
||||
>
|
||||
{isSectionsExpanded
|
||||
? intl.formatMessage(messages.collapseAllButton)
|
||||
: intl.formatMessage(messages.expandAllButton)}
|
||||
</Button>
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={(
|
||||
<Tooltip id={intl.formatMessage(messages.viewLiveButtonTooltip)}>
|
||||
{intl.formatMessage(messages.viewLiveButtonTooltip)}
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
href={lmsLink}
|
||||
target="_blank"
|
||||
variant="outline-primary"
|
||||
>
|
||||
{intl.formatMessage(messages.viewLiveButton)}
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
HeaderNavigations.propTypes = {
|
||||
isReIndexShow: PropTypes.bool.isRequired,
|
||||
isSectionsExpanded: PropTypes.bool.isRequired,
|
||||
isDisabledReindexButton: PropTypes.bool.isRequired,
|
||||
headerNavigationsActions: PropTypes.shape({
|
||||
handleNewSection: PropTypes.func.isRequired,
|
||||
handleReIndex: PropTypes.func.isRequired,
|
||||
handleExpandAll: PropTypes.func.isRequired,
|
||||
lmsLink: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default HeaderNavigations;
|
||||
@@ -0,0 +1,4 @@
|
||||
.header-navigations {
|
||||
display: flex;
|
||||
gap: .75rem;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import HeaderNavigations from './HeaderNavigations';
|
||||
import messages from './messages';
|
||||
|
||||
const handleNewSectionMock = jest.fn();
|
||||
const handleReIndexMock = jest.fn();
|
||||
const handleExpandAllMock = jest.fn();
|
||||
|
||||
const headerNavigationsActions = {
|
||||
handleNewSection: handleNewSectionMock,
|
||||
handleReIndex: handleReIndexMock,
|
||||
handleExpandAll: handleExpandAllMock,
|
||||
lmsLink: '',
|
||||
};
|
||||
|
||||
const renderComponent = (props) => render(
|
||||
<IntlProvider locale="en">
|
||||
<HeaderNavigations
|
||||
headerNavigationsActions={headerNavigationsActions}
|
||||
isSectionsExpanded={false}
|
||||
isDisabledReindexButton={false}
|
||||
isReIndexShow
|
||||
{...props}
|
||||
/>
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
describe('<HeaderNavigations />', () => {
|
||||
it('render HeaderNavigations component correctly', () => {
|
||||
const { getByRole } = renderComponent();
|
||||
|
||||
expect(getByRole('button', { name: messages.newSectionButton.defaultMessage })).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.reindexButton.defaultMessage })).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.expandAllButton.defaultMessage })).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.viewLiveButton.defaultMessage })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render HeaderNavigations component with isReIndexShow is false correctly', () => {
|
||||
const { getByRole, queryByRole } = renderComponent({ isReIndexShow: false });
|
||||
|
||||
expect(getByRole('button', { name: messages.newSectionButton.defaultMessage })).toBeInTheDocument();
|
||||
expect(queryByRole('button', { name: messages.reindexButton.defaultMessage })).not.toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.expandAllButton.defaultMessage })).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: messages.viewLiveButton.defaultMessage })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls the correct handlers when clicking buttons', () => {
|
||||
const { getByRole } = renderComponent();
|
||||
|
||||
const newSectionButton = getByRole('button', { name: messages.newSectionButton.defaultMessage });
|
||||
fireEvent.click(newSectionButton);
|
||||
expect(handleNewSectionMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
const reIndexButton = getByRole('button', { name: messages.reindexButton.defaultMessage });
|
||||
fireEvent.click(reIndexButton);
|
||||
expect(handleReIndexMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
const expandAllButton = getByRole('button', { name: messages.expandAllButton.defaultMessage });
|
||||
fireEvent.click(expandAllButton);
|
||||
expect(handleExpandAllMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('render collapse button correctly', () => {
|
||||
const { getByRole } = renderComponent({
|
||||
isSectionsExpanded: true,
|
||||
});
|
||||
|
||||
expect(getByRole('button', { name: messages.collapseAllButton.defaultMessage })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render expand button correctly', () => {
|
||||
const { getByRole } = renderComponent({
|
||||
isSectionsExpanded: false,
|
||||
});
|
||||
|
||||
expect(getByRole('button', { name: messages.expandAllButton.defaultMessage })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
38
src/course-outline/header-navigations/messages.js
Normal file
38
src/course-outline/header-navigations/messages.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
newSectionButton: {
|
||||
id: 'course-authoring.course-outline.header-navigations.button.new-section',
|
||||
defaultMessage: 'New section',
|
||||
},
|
||||
newSectionButtonTooltip: {
|
||||
id: 'course-authoring.course-outline.header-navigations.button.new-section.tooltip',
|
||||
defaultMessage: 'Click to add a new section',
|
||||
},
|
||||
reindexButton: {
|
||||
id: 'course-authoring.course-outline.header-navigations.button.reindex',
|
||||
defaultMessage: 'Reindex',
|
||||
},
|
||||
reindexButtonTooltip: {
|
||||
id: 'course-authoring.course-outline.header-navigations.button.reindex.tooltip',
|
||||
defaultMessage: 'Reindex current course',
|
||||
},
|
||||
expandAllButton: {
|
||||
id: 'course-authoring.course-outline.header-navigations.button.expand-all',
|
||||
defaultMessage: 'Expand all',
|
||||
},
|
||||
collapseAllButton: {
|
||||
id: 'course-authoring.course-outline.header-navigations.button.collapse-all',
|
||||
defaultMessage: 'Collapse all',
|
||||
},
|
||||
viewLiveButton: {
|
||||
id: 'course-authoring.course-outline.header-navigations.button.view-live',
|
||||
defaultMessage: 'View live',
|
||||
},
|
||||
viewLiveButtonTooltip: {
|
||||
id: 'course-authoring.course-outline.header-navigations.button.view-live.tooltip',
|
||||
defaultMessage: 'Click to open the courseware in the LMS in a new tab',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
99
src/course-outline/hooks.jsx
Normal file
99
src/course-outline/hooks.jsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useToggle } from '@edx/paragon';
|
||||
|
||||
import { RequestStatus } from '../data/constants';
|
||||
import { updateSavingStatus } from './data/slice';
|
||||
import {
|
||||
getLoadingStatus,
|
||||
getOutlineIndexData,
|
||||
getSavingStatus,
|
||||
getStatusBarData,
|
||||
} from './data/selectors';
|
||||
import {
|
||||
enableCourseHighlightsEmailsQuery,
|
||||
fetchCourseBestPracticesQuery,
|
||||
fetchCourseLaunchQuery,
|
||||
fetchCourseOutlineIndexQuery,
|
||||
fetchCourseReindexQuery,
|
||||
} from './data/thunk';
|
||||
|
||||
const useCourseOutline = ({ courseId }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { reindexLink, lmsLink } = useSelector(getOutlineIndexData);
|
||||
const { outlineIndexLoadingStatus, reIndexLoadingStatus } = useSelector(getLoadingStatus);
|
||||
const statusBarData = useSelector(getStatusBarData);
|
||||
const savingStatus = useSelector(getSavingStatus);
|
||||
|
||||
const [isEnableHighlightsModalOpen, openEnableHighlightsModal, closeEnableHighlightsModal] = useToggle(false);
|
||||
const [isSectionsExpanded, setSectionsExpanded] = useState(false);
|
||||
const [isDisabledReindexButton, setDisableReindexButton] = useState(false);
|
||||
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
|
||||
const [showErrorAlert, setShowErrorAlert] = useState(false);
|
||||
|
||||
const headerNavigationsActions = {
|
||||
handleNewSection: () => {
|
||||
// TODO add handler
|
||||
},
|
||||
handleReIndex: () => {
|
||||
setDisableReindexButton(true);
|
||||
setShowSuccessAlert(false);
|
||||
setShowErrorAlert(false);
|
||||
|
||||
dispatch(fetchCourseReindexQuery(courseId, reindexLink)).then(() => {
|
||||
setDisableReindexButton(false);
|
||||
});
|
||||
},
|
||||
handleExpandAll: () => {
|
||||
setSectionsExpanded((prevState) => !prevState);
|
||||
},
|
||||
lmsLink,
|
||||
};
|
||||
|
||||
const handleEnableHighlightsSubmit = () => {
|
||||
dispatch(enableCourseHighlightsEmailsQuery(courseId));
|
||||
closeEnableHighlightsModal();
|
||||
};
|
||||
|
||||
const handleInternetConnectionFailed = () => {
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchCourseOutlineIndexQuery(courseId));
|
||||
dispatch(fetchCourseBestPracticesQuery({ courseId }));
|
||||
dispatch(fetchCourseLaunchQuery({ courseId }));
|
||||
}, [courseId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (reIndexLoadingStatus === RequestStatus.FAILED) {
|
||||
setShowErrorAlert(true);
|
||||
}
|
||||
|
||||
if (reIndexLoadingStatus === RequestStatus.SUCCESSFUL) {
|
||||
setShowSuccessAlert(true);
|
||||
}
|
||||
}, [reIndexLoadingStatus]);
|
||||
|
||||
return {
|
||||
savingStatus,
|
||||
isLoading: outlineIndexLoadingStatus === RequestStatus.IN_PROGRESS,
|
||||
isReIndexShow: Boolean(reindexLink),
|
||||
showSuccessAlert,
|
||||
showErrorAlert,
|
||||
isDisabledReindexButton,
|
||||
isSectionsExpanded,
|
||||
headerNavigationsActions,
|
||||
handleEnableHighlightsSubmit,
|
||||
statusBarData,
|
||||
isEnableHighlightsModalOpen,
|
||||
openEnableHighlightsModal,
|
||||
closeEnableHighlightsModal,
|
||||
isInternetConnectionAlertFailed: savingStatus === RequestStatus.FAILED,
|
||||
handleInternetConnectionFailed,
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export { useCourseOutline };
|
||||
2
src/course-outline/index.js
Normal file
2
src/course-outline/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export { default as CourseOutline } from './CourseOutline';
|
||||
34
src/course-outline/messages.js
Normal file
34
src/course-outline/messages.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
headingTitle: {
|
||||
id: 'course-authoring.course-outline.headingTitle',
|
||||
defaultMessage: 'Course outline',
|
||||
},
|
||||
headingSubtitle: {
|
||||
id: 'course-authoring.course-outline.subTitle',
|
||||
defaultMessage: 'Content',
|
||||
},
|
||||
alertSuccessTitle: {
|
||||
id: 'course-authoring.course-outline.reindex.alert.success.title',
|
||||
defaultMessage: 'Course index',
|
||||
},
|
||||
alertSuccessDescription: {
|
||||
id: 'course-authoring.course-outline.reindex.alert.success.description',
|
||||
defaultMessage: 'Course has been successfully reindexed.',
|
||||
},
|
||||
alertSuccessAriaLabelledby: {
|
||||
id: 'course-authoring.course-outline.reindex.alert.success.aria.labelledby',
|
||||
defaultMessage: 'alert-confirmation-title',
|
||||
},
|
||||
alertSuccessAriaDescribedby: {
|
||||
id: 'course-authoring.course-outline.reindex.alert.success.aria.describedby',
|
||||
defaultMessage: 'alert-confirmation-description',
|
||||
},
|
||||
alertErrorTitle: {
|
||||
id: 'course-authoring.course-outline.reindex.alert.error.title',
|
||||
defaultMessage: 'There were errors reindexing course.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
66
src/course-outline/outline-sidebar/OutlineSidebar.jsx
Normal file
66
src/course-outline/outline-sidebar/OutlineSidebar.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { HelpSidebar } from '../../generic/help-sidebar';
|
||||
import { useHelpUrls } from '../../help-urls/hooks';
|
||||
import { getFormattedSidebarMessages } from './utils';
|
||||
|
||||
const OutlineSideBar = ({ courseId }) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
visibility: learnMoreVisibilityUrl,
|
||||
grading: learnMoreGradingUrl,
|
||||
outline: learnMoreOutlineUrl,
|
||||
} = useHelpUrls(['visibility', 'grading', 'outline']);
|
||||
|
||||
const sidebarMessages = getFormattedSidebarMessages(
|
||||
{
|
||||
learnMoreGradingUrl,
|
||||
learnMoreOutlineUrl,
|
||||
learnMoreVisibilityUrl,
|
||||
},
|
||||
intl,
|
||||
);
|
||||
|
||||
return (
|
||||
<HelpSidebar
|
||||
intl={intl}
|
||||
courseId={courseId}
|
||||
showOtherSettings={false}
|
||||
className="outline-sidebar mt-4"
|
||||
data-testid="outline-sidebar"
|
||||
>
|
||||
{sidebarMessages.map(({ title, descriptions, link }, index) => {
|
||||
const isLastSection = index === sidebarMessages.length - 1;
|
||||
|
||||
return (
|
||||
<div className="outline-sidebar-section" key={title}>
|
||||
<h4 className="help-sidebar-about-title">{title}</h4>
|
||||
{descriptions.map((description) => (
|
||||
<p className="help-sidebar-about-descriptions" key={description}>{description}</p>
|
||||
))}
|
||||
{Boolean(link) && Boolean(link.href) && (
|
||||
<Hyperlink
|
||||
className="small"
|
||||
destination={link.href}
|
||||
target="_blank"
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
{link.text}
|
||||
</Hyperlink>
|
||||
)}
|
||||
{!isLastSection && <hr className="my-3.5" />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</HelpSidebar>
|
||||
);
|
||||
};
|
||||
|
||||
OutlineSideBar.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default OutlineSideBar;
|
||||
84
src/course-outline/outline-sidebar/OutlineSidebar.test.jsx
Normal file
84
src/course-outline/outline-sidebar/OutlineSidebar.test.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import { helpUrls } from '../../help-urls/__mocks__';
|
||||
import { getHelpUrlsApiUrl } from '../../help-urls/data/api';
|
||||
import initializeStore from '../../store';
|
||||
import OutlineSidebar from './OutlineSidebar';
|
||||
import messages from './messages';
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
const mockPathname = '/foo-bar';
|
||||
const courseId = '123';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: () => ({
|
||||
pathname: mockPathname,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||
useIntl: () => ({
|
||||
formatMessage: (message) => message.defaultMessage,
|
||||
}),
|
||||
}));
|
||||
|
||||
const renderComponent = (props) => render(
|
||||
<AppProvider store={store} messages={{}}>
|
||||
<IntlProvider locale="en">
|
||||
<OutlineSidebar courseId={courseId} {...props} />
|
||||
</IntlProvider>
|
||||
</AppProvider>,
|
||||
);
|
||||
|
||||
describe('<OutlineSidebar />', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock
|
||||
.onGet(getHelpUrlsApiUrl())
|
||||
.reply(200, helpUrls);
|
||||
});
|
||||
|
||||
it('render OutlineSidebar component correctly', async () => {
|
||||
const { getByText } = renderComponent();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText(messages.section_1_title.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.section_1_descriptions_1.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.section_1_descriptions_2.defaultMessage)).toBeInTheDocument();
|
||||
|
||||
expect(getByText(messages.section_2_title.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.section_2_descriptions_1.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.section_2_link.defaultMessage)).toBeInTheDocument();
|
||||
|
||||
expect(getByText(messages.section_3_title.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.section_3_descriptions_1.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.section_3_link.defaultMessage)).toBeInTheDocument();
|
||||
|
||||
expect(getByText(messages.section_4_title.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.section_4_descriptions_1.defaultMessage)).toBeInTheDocument();
|
||||
|
||||
expect(getByText(messages.section_4_descriptions_2.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.section_4_descriptions_3.defaultMessage)).toBeInTheDocument();
|
||||
|
||||
expect(getByText(messages.section_4_link.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
70
src/course-outline/outline-sidebar/messages.js
Normal file
70
src/course-outline/outline-sidebar/messages.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
section_1_title: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-1.title',
|
||||
defaultMessage: 'Creating your course organization',
|
||||
},
|
||||
section_1_descriptions_1: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-1.descriptions-1',
|
||||
defaultMessage: 'You add sections, subsections, and units directly in the outline.',
|
||||
},
|
||||
section_1_descriptions_2: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-1.descriptions-2',
|
||||
defaultMessage: 'Create a section, then add subsections and units. Open a unit to add course components.',
|
||||
},
|
||||
section_2_title: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-2.title',
|
||||
defaultMessage: 'Reorganizing your course',
|
||||
},
|
||||
section_2_descriptions_1: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-2.descriptions-1',
|
||||
defaultMessage: 'Drag sections, subsections, and units to new locations in the outline.',
|
||||
},
|
||||
section_2_link: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-2.link',
|
||||
defaultMessage: 'Learn more about the course outline',
|
||||
},
|
||||
section_3_title: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-3.title',
|
||||
defaultMessage: 'Setting release dates and grading policies',
|
||||
},
|
||||
section_3_descriptions_1: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-3.descriptions-1',
|
||||
defaultMessage: 'Select the Configure icon for a section or subsection to set its release date. When you configure a subsection, you can also set the grading policy and due date.',
|
||||
},
|
||||
section_3_link: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-3.link',
|
||||
defaultMessage: 'Learn more about grading policy settings',
|
||||
},
|
||||
section_4_title: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-4.title',
|
||||
defaultMessage: 'Changing the content learners see',
|
||||
},
|
||||
section_4_descriptions_1: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-4.descriptions-1',
|
||||
defaultMessage: 'To publish draft content, select the Publish icon for a section, subsection, or unit.',
|
||||
},
|
||||
section_4_descriptions_2: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-4.descriptions-2',
|
||||
defaultMessage: 'To make a section, subsection, or unit unavailable to learners, select the Configure icon for that level, then select the appropriate {hide} option. Grades for hidden sections, subsections, and units are not included in grade calculations.',
|
||||
},
|
||||
section_4_descriptions_2_hide: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-4.descriptions-2.hide',
|
||||
defaultMessage: 'Hide',
|
||||
},
|
||||
section_4_descriptions_3: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-4.descriptions-3',
|
||||
defaultMessage: 'To hide the content of a subsection from learners after the subsection due date has passed, select the Configure icon for a subsection, then select {hide}. Grades for the subsection remain included in grade calculations.',
|
||||
},
|
||||
section_4_descriptions_3_hide: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-4.descriptions-3.hide',
|
||||
defaultMessage: 'Hide content after due date',
|
||||
},
|
||||
section_4_link: {
|
||||
id: 'course-authoring.course-outline.sidebar.section-4.link',
|
||||
defaultMessage: 'Learn more about content visibility settings',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
69
src/course-outline/outline-sidebar/utils.jsx
Normal file
69
src/course-outline/outline-sidebar/utils.jsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import messages from './messages';
|
||||
|
||||
/**
|
||||
* Get formatted sidebar messages for render
|
||||
* @param {object} docsLinks - Docs links object from store
|
||||
* @returns {Array<{
|
||||
* title: string,
|
||||
* descriptions: Array<string>,
|
||||
* link?: {
|
||||
* text: string,
|
||||
* href: string
|
||||
* }
|
||||
* }>}
|
||||
*/
|
||||
const getFormattedSidebarMessages = (docsLinks, intl) => {
|
||||
const { learnMoreOutlineUrl, learnMoreGradingUrl, learnMoreVisibilityUrl } = docsLinks;
|
||||
|
||||
return [
|
||||
{
|
||||
title: intl.formatMessage(messages.section_1_title),
|
||||
descriptions: [
|
||||
intl.formatMessage(messages.section_1_descriptions_1),
|
||||
intl.formatMessage(messages.section_1_descriptions_2),
|
||||
],
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage(messages.section_2_title),
|
||||
descriptions: [
|
||||
intl.formatMessage(messages.section_2_descriptions_1),
|
||||
],
|
||||
link: {
|
||||
text: intl.formatMessage(messages.section_2_link),
|
||||
href: learnMoreOutlineUrl,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage(messages.section_3_title),
|
||||
descriptions: [
|
||||
intl.formatMessage(messages.section_3_descriptions_1),
|
||||
],
|
||||
link: {
|
||||
text: intl.formatMessage(messages.section_3_link),
|
||||
href: learnMoreGradingUrl,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage(messages.section_4_title),
|
||||
descriptions: [
|
||||
intl.formatMessage(messages.section_4_descriptions_1),
|
||||
intl.formatMessage(
|
||||
messages.section_4_descriptions_2,
|
||||
{ hide: <strong>{intl.formatMessage(messages.section_4_descriptions_2_hide)}</strong> },
|
||||
),
|
||||
intl.formatMessage(
|
||||
messages.section_4_descriptions_3,
|
||||
{ hide: <strong>{intl.formatMessage(messages.section_4_descriptions_3_hide)}</strong> },
|
||||
),
|
||||
],
|
||||
link: {
|
||||
text: intl.formatMessage(messages.section_4_link),
|
||||
href: learnMoreVisibilityUrl,
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export { getFormattedSidebarMessages };
|
||||
116
src/course-outline/status-bar/StatusBar.jsx
Normal file
116
src/course-outline/status-bar/StatusBar.jsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button, Hyperlink, Stack } from '@edx/paragon';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const StatusBar = ({
|
||||
statusBarData,
|
||||
isLoading,
|
||||
courseId,
|
||||
openEnableHighlightsModal,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { config } = useContext(AppContext);
|
||||
|
||||
const {
|
||||
courseReleaseDate,
|
||||
highlightsEnabledForMessaging,
|
||||
highlightsDocUrl,
|
||||
checklist,
|
||||
isSelfPaced,
|
||||
} = statusBarData;
|
||||
|
||||
const {
|
||||
completedCourseLaunchChecks,
|
||||
completedCourseBestPracticesChecks,
|
||||
totalCourseLaunchChecks,
|
||||
totalCourseBestPracticesChecks,
|
||||
} = checklist;
|
||||
|
||||
const checkListTitle = `${completedCourseLaunchChecks + completedCourseBestPracticesChecks}/${totalCourseLaunchChecks + totalCourseBestPracticesChecks}`;
|
||||
const checklistDestination = new URL(`checklists/${courseId}`, config.STUDIO_BASE_URL).href;
|
||||
const scheduleDestination = new URL(`course/${courseId}/settings/details#schedule`, config.BASE_URL).href;
|
||||
|
||||
if (isLoading) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack direction="horizontal" gap={3.5} className="outline-status-bar" data-testid="outline-status-bar">
|
||||
<div className="outline-status-bar__item">
|
||||
<h5>{intl.formatMessage(messages.startDateTitle)}</h5>
|
||||
<Hyperlink
|
||||
className="small"
|
||||
destination={scheduleDestination}
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
{courseReleaseDate}
|
||||
</Hyperlink>
|
||||
</div>
|
||||
<div className="outline-status-bar__item">
|
||||
<h5>{intl.formatMessage(messages.pacingTypeTitle)}</h5>
|
||||
<span className="small">
|
||||
{isSelfPaced
|
||||
? intl.formatMessage(messages.pacingTypeSelfPaced)
|
||||
: intl.formatMessage(messages.pacingTypeInstructorPaced)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="outline-status-bar__item mr-4">
|
||||
<h5>{intl.formatMessage(messages.checklistTitle)}</h5>
|
||||
<Hyperlink
|
||||
className="small"
|
||||
destination={checklistDestination}
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
{checkListTitle} {intl.formatMessage(messages.checklistCompleted)}
|
||||
</Hyperlink>
|
||||
</div>
|
||||
<div className="outline-status-bar__item ml-4">
|
||||
<h5>{intl.formatMessage(messages.highlightEmailsTitle)}</h5>
|
||||
<div className="d-flex align-items-end">
|
||||
{highlightsEnabledForMessaging ? (
|
||||
<span data-testid="highlights-enabled-span" className="small">
|
||||
{intl.formatMessage(messages.highlightEmailsEnabled)}
|
||||
</span>
|
||||
) : (
|
||||
<Button size="sm" onClick={openEnableHighlightsModal}>
|
||||
{intl.formatMessage(messages.highlightEmailsButton)}
|
||||
</Button>
|
||||
)}
|
||||
<Hyperlink
|
||||
className="small ml-2"
|
||||
destination={highlightsDocUrl}
|
||||
target="_blank"
|
||||
showLaunchIcon={false}
|
||||
>
|
||||
{intl.formatMessage(messages.highlightEmailsLink)}
|
||||
</Hyperlink>
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
StatusBar.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
openEnableHighlightsModal: PropTypes.func.isRequired,
|
||||
statusBarData: PropTypes.shape({
|
||||
courseReleaseDate: PropTypes.string.isRequired,
|
||||
isSelfPaced: PropTypes.bool.isRequired,
|
||||
checklist: PropTypes.shape({
|
||||
totalCourseLaunchChecks: PropTypes.number.isRequired,
|
||||
completedCourseLaunchChecks: PropTypes.number.isRequired,
|
||||
totalCourseBestPracticesChecks: PropTypes.number.isRequired,
|
||||
completedCourseBestPracticesChecks: PropTypes.number.isRequired,
|
||||
}),
|
||||
highlightsEnabledForMessaging: PropTypes.bool.isRequired,
|
||||
highlightsDocUrl: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default StatusBar;
|
||||
12
src/course-outline/status-bar/StatusBar.scss
Normal file
12
src/course-outline/status-bar/StatusBar.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
.outline-status-bar {
|
||||
.outline-status-bar__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
min-height: 3.75rem;
|
||||
|
||||
& h5 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
112
src/course-outline/status-bar/StatusBar.test.jsx
Normal file
112
src/course-outline/status-bar/StatusBar.test.jsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
|
||||
import StatusBar from './StatusBar';
|
||||
import messages from './messages';
|
||||
import initializeStore from '../../store';
|
||||
|
||||
let store;
|
||||
const mockPathname = '/foo-bar';
|
||||
const courseId = '123';
|
||||
const isLoading = false;
|
||||
const openEnableHighlightsModalMock = jest.fn();
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: () => ({
|
||||
pathname: mockPathname,
|
||||
}),
|
||||
}));
|
||||
|
||||
const statusBarData = {
|
||||
courseReleaseDate: 'Feb 05, 2013 at 05:00 UTC',
|
||||
isSelfPaced: true,
|
||||
checklist: {
|
||||
totalCourseLaunchChecks: 5,
|
||||
completedCourseLaunchChecks: 1,
|
||||
totalCourseBestPracticesChecks: 4,
|
||||
completedCourseBestPracticesChecks: 1,
|
||||
},
|
||||
highlightsEnabledForMessaging: true,
|
||||
highlightsDocUrl: 'https://example.com/highlights-doc',
|
||||
};
|
||||
|
||||
const renderComponent = (props) => render(
|
||||
<AppProvider store={store} messages={{}}>
|
||||
<IntlProvider locale="en">
|
||||
<StatusBar
|
||||
courseId={courseId}
|
||||
isLoading={isLoading}
|
||||
openEnableHighlightsModal={openEnableHighlightsModalMock}
|
||||
statusBarData={statusBarData}
|
||||
{...props}
|
||||
/>
|
||||
</IntlProvider>
|
||||
</AppProvider>,
|
||||
);
|
||||
|
||||
describe('<StatusBar />', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
});
|
||||
|
||||
it('renders StatusBar component correctly', () => {
|
||||
const { getByText } = renderComponent();
|
||||
|
||||
expect(getByText(messages.startDateTitle.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(statusBarData.courseReleaseDate)).toBeInTheDocument();
|
||||
|
||||
expect(getByText(messages.pacingTypeTitle.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.pacingTypeSelfPaced.defaultMessage)).toBeInTheDocument();
|
||||
|
||||
expect(getByText(messages.checklistTitle.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(`2/9 ${messages.checklistCompleted.defaultMessage}`)).toBeInTheDocument();
|
||||
|
||||
expect(getByText(messages.highlightEmailsTitle.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.highlightEmailsLink.defaultMessage)).toBeInTheDocument();
|
||||
expect(getByText(messages.highlightEmailsEnabled.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders StatusBar when isSelfPaced is false', () => {
|
||||
const { getByText } = renderComponent({
|
||||
statusBarData: {
|
||||
...statusBarData,
|
||||
isSelfPaced: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(getByText(messages.pacingTypeInstructorPaced.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls openEnableHighlightsModal function when the "Enable Highlight Emails" button is clicked', () => {
|
||||
const { getByRole } = renderComponent({
|
||||
statusBarData: {
|
||||
...statusBarData,
|
||||
highlightsEnabledForMessaging: false,
|
||||
},
|
||||
});
|
||||
|
||||
const enableHighlightsButton = getByRole('button', { name: messages.highlightEmailsButton.defaultMessage });
|
||||
fireEvent.click(enableHighlightsButton);
|
||||
expect(openEnableHighlightsModalMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('not render component when isLoading is true', () => {
|
||||
const { queryByTestId } = renderComponent({
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
expect(queryByTestId('outline-status-bar')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
46
src/course-outline/status-bar/messages.js
Normal file
46
src/course-outline/status-bar/messages.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
startDateTitle: {
|
||||
id: 'course-authoring.course-outline.status-bar.start-date',
|
||||
defaultMessage: 'Start date',
|
||||
},
|
||||
pacingTypeTitle: {
|
||||
id: 'course-authoring.course-outline.status-bar.pacing-type',
|
||||
defaultMessage: 'Pacing type',
|
||||
},
|
||||
pacingTypeSelfPaced: {
|
||||
id: 'course-authoring.course-outline.status-bar.pacing-type.self-paced',
|
||||
defaultMessage: 'Self-paced',
|
||||
},
|
||||
pacingTypeInstructorPaced: {
|
||||
id: 'course-authoring.course-outline.status-bar.pacing-type.instructor-Paced',
|
||||
defaultMessage: 'Instructor-paced',
|
||||
},
|
||||
checklistTitle: {
|
||||
id: 'course-authoring.course-outline.status-bar.checklists',
|
||||
defaultMessage: 'Checklists',
|
||||
},
|
||||
checklistCompleted: {
|
||||
id: 'course-authoring.course-outline.status-bar.checklists.completed',
|
||||
defaultMessage: 'completed',
|
||||
},
|
||||
highlightEmailsTitle: {
|
||||
id: 'course-authoring.course-outline.status-bar.highlight-emails',
|
||||
defaultMessage: 'Course highlight emails',
|
||||
},
|
||||
highlightEmailsButton: {
|
||||
id: 'course-authoring.course-outline.status-bar.highlight-emails.button',
|
||||
defaultMessage: 'Enable now',
|
||||
},
|
||||
highlightEmailsEnabled: {
|
||||
id: 'course-authoring.course-outline.status-bar.highlight-emails.enabled',
|
||||
defaultMessage: 'Enabled',
|
||||
},
|
||||
highlightEmailsLink: {
|
||||
id: 'course-authoring.course-outline.status-bar.highlight-emails.link',
|
||||
defaultMessage: 'Learn more',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
106
src/course-outline/utils/courseChecklistValidators.js
Normal file
106
src/course-outline/utils/courseChecklistValidators.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* The utilities are taken from the https://github.com/openedx/studio-frontend repository.
|
||||
* Perform a minor refactoring of the functions while preserving their original functionality.
|
||||
*/
|
||||
|
||||
export const hasWelcomeMessage = (updates) => updates.hasUpdate;
|
||||
|
||||
export const hasGradingPolicy = (grades) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
const { hasGradingPolicy, sumOfWeights } = grades;
|
||||
|
||||
return hasGradingPolicy && parseFloat(sumOfWeights.toPrecision(2), 10) === 1.0;
|
||||
};
|
||||
|
||||
export const hasCertificate = (certificates) => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
const { isActivated, hasCertificate } = certificates;
|
||||
|
||||
return isActivated && hasCertificate;
|
||||
};
|
||||
|
||||
export const hasDates = (dates) => {
|
||||
const { hasStartDate, hasEndDate } = dates;
|
||||
|
||||
return hasStartDate && hasEndDate;
|
||||
};
|
||||
|
||||
export const hasAssignmentDeadlines = (assignments, dates) => {
|
||||
const {
|
||||
totalNumber,
|
||||
assignmentsWithDatesBeforeStart,
|
||||
assignmentsWithDatesAfterEnd,
|
||||
assignmentsWithOraDatesBeforeStart,
|
||||
assignmentsWithOraDatesAfterEnd,
|
||||
} = assignments;
|
||||
|
||||
if (!hasDates(dates)) {
|
||||
return false;
|
||||
}
|
||||
if (totalNumber === 0) {
|
||||
return false;
|
||||
}
|
||||
if (assignmentsWithDatesBeforeStart.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (assignmentsWithDatesAfterEnd.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (assignmentsWithOraDatesBeforeStart.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (assignmentsWithOraDatesAfterEnd.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const hasShortVideoDuration = (videos) => {
|
||||
const { totalNumber, durations } = videos;
|
||||
|
||||
if (totalNumber === 0) {
|
||||
return true;
|
||||
}
|
||||
if (totalNumber > 0 && durations.median <= 600) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const hasMobileFriendlyVideos = (videos) => {
|
||||
const { totalNumber, numMobileEncoded } = videos;
|
||||
|
||||
if (totalNumber === 0) {
|
||||
return true;
|
||||
}
|
||||
if (totalNumber > 0 && (numMobileEncoded / totalNumber) >= 0.9) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const hasDiverseSequences = (subsections) => {
|
||||
const { totalVisible, numWithOneBlockType } = subsections;
|
||||
|
||||
if (totalVisible === 0) {
|
||||
return false;
|
||||
}
|
||||
if (totalVisible > 0) {
|
||||
return ((numWithOneBlockType / totalVisible) < 0.2);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const hasWeeklyHighlights = (sections) => {
|
||||
const { highlightsActiveForCourse, highlightsEnabled } = sections;
|
||||
|
||||
return highlightsActiveForCourse && highlightsEnabled;
|
||||
};
|
||||
|
||||
export const hasShortUnitDepth = (units) => units.numBlocks.median <= 3;
|
||||
|
||||
export const hasProctoringEscalationEmail = (proctoring) => proctoring.hasProctoringEscalationEmail;
|
||||
297
src/course-outline/utils/courseChecklistValidators.test.js
Normal file
297
src/course-outline/utils/courseChecklistValidators.test.js
Normal file
@@ -0,0 +1,297 @@
|
||||
import * as validators from './courseChecklistValidators';
|
||||
|
||||
describe('courseCheckValidators utility functions', () => {
|
||||
describe('hasWelcomeMessage', () => {
|
||||
it('returns true when course run has an update', () => {
|
||||
expect(validators.hasWelcomeMessage({ hasUpdate: true })).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when course run does not have an update', () => {
|
||||
expect(validators.hasWelcomeMessage({ hasUpdate: false })).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasGradingPolicy', () => {
|
||||
it('returns true when sum of weights is 1', () => {
|
||||
expect(validators.hasGradingPolicy(
|
||||
{ hasGradingPolicy: true, sumOfWeights: 1 },
|
||||
)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns true when sum of weights is not 1 due to floating point approximation (1.00004)', () => {
|
||||
expect(validators.hasGradingPolicy(
|
||||
{ hasGradingPolicy: true, sumOfWeights: 1.00004 },
|
||||
)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when sum of weights is not 1', () => {
|
||||
expect(validators.hasGradingPolicy(
|
||||
{ hasGradingPolicy: true, sumOfWeights: 2 },
|
||||
)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns true when hasGradingPolicy is true', () => {
|
||||
expect(validators.hasGradingPolicy(
|
||||
{ hasGradingPolicy: true, sumOfWeights: 1 },
|
||||
)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when hasGradingPolicy is false', () => {
|
||||
expect(validators.hasGradingPolicy(
|
||||
{ hasGradingPolicy: false, sumOfWeights: 1 },
|
||||
)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasCertificate', () => {
|
||||
it('returns true when certificates are activated and course run has a certificate', () => {
|
||||
expect(validators.hasCertificate({ isActivated: true, hasCertificate: true }))
|
||||
.toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when certificates are not activated and course run has a certificate', () => {
|
||||
expect(validators.hasCertificate({ isActivated: false, hasCertificate: true }))
|
||||
.toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false when certificates are activated and course run does not have a certificate', () => {
|
||||
expect(validators.hasCertificate({ isActivated: true, hasCertificate: false }))
|
||||
.toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false when certificates are not activated and course run does not have a certificate', () => {
|
||||
expect(validators.hasCertificate({ isActivated: false, hasCertificate: false }))
|
||||
.toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasDates', () => {
|
||||
it('returns true when course run has start date and end date', () => {
|
||||
expect(validators.hasDates({ hasStartDate: true, hasEndDate: true })).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when course run has no start date and end date', () => {
|
||||
expect(validators.hasDates({ hasStartDate: false, hasEndDate: true })).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns true when course run has start date and no end date', () => {
|
||||
expect(validators.hasDates({ hasStartDate: true, hasEndDate: false })).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns true when course run has no start date and no end date', () => {
|
||||
expect(validators.hasDates({ hasStartDate: false, hasEndDate: false })).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasAssignmentDeadlines', () => {
|
||||
it('returns true when a course run has start and end date and all assignments are within range', () => {
|
||||
expect(validators.hasAssignmentDeadlines(
|
||||
{
|
||||
assignmentsWithDatesBeforeStart: 0,
|
||||
assignmentsWithDatesAfterEnd: 0,
|
||||
assignmentsWithOraDatesAfterEnd: 0,
|
||||
assignmentsWithOraDatesBeforeStart: 0,
|
||||
},
|
||||
{
|
||||
hasStartDate: true,
|
||||
hasEndDate: true,
|
||||
},
|
||||
)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when a course run has no start and no end date', () => {
|
||||
expect(validators.hasAssignmentDeadlines(
|
||||
{},
|
||||
{
|
||||
hasStartDate: false,
|
||||
hasEndDate: false,
|
||||
},
|
||||
)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false when a course has start and end date and no assignments', () => {
|
||||
expect(validators.hasAssignmentDeadlines(
|
||||
{
|
||||
totalNumber: 0,
|
||||
},
|
||||
{
|
||||
hasStartDate: true,
|
||||
hasEndDate: true,
|
||||
},
|
||||
)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false when a course run has start and end date and assignments before start', () => {
|
||||
expect(validators.hasAssignmentDeadlines(
|
||||
{
|
||||
assignmentsWithDatesBeforeStart: ['test'],
|
||||
assignmentsWithDatesAfterEnd: 0,
|
||||
assignmentsWithOraDatesAfterEnd: 0,
|
||||
assignmentsWithOraDatesBeforeStart: 0,
|
||||
},
|
||||
{
|
||||
hasStartDate: true,
|
||||
hasEndDate: true,
|
||||
},
|
||||
)).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false when a course run has start and end date and assignments after end', () => {
|
||||
expect(validators.hasAssignmentDeadlines(
|
||||
{
|
||||
assignmentsWithDatesBeforeStart: 0,
|
||||
assignmentsWithDatesAfterEnd: ['test'],
|
||||
assignmentsWithOraDatesAfterEnd: 0,
|
||||
assignmentsWithOraDatesBeforeStart: 0,
|
||||
},
|
||||
{
|
||||
hasStartDate: true,
|
||||
hasEndDate: true,
|
||||
},
|
||||
)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
'returns false when a course run has start and end date and an ora with a date before start',
|
||||
() => {
|
||||
expect(validators.hasAssignmentDeadlines(
|
||||
{
|
||||
assignmentsWithDatesBeforeStart: 0,
|
||||
assignmentsWithDatesAfterEnd: 0,
|
||||
assignmentsWithOraDatesAfterEnd: 0,
|
||||
assignmentsWithOraDatesBeforeStart: ['test'],
|
||||
},
|
||||
{
|
||||
hasStartDate: true,
|
||||
hasEndDate: true,
|
||||
},
|
||||
)).toEqual(false);
|
||||
},
|
||||
);
|
||||
|
||||
it(
|
||||
'returns false when a course run has start and end date and an ora with a date after end',
|
||||
() => {
|
||||
expect(validators.hasAssignmentDeadlines(
|
||||
{
|
||||
assignmentsWithDatesBeforeStart: 0,
|
||||
assignmentsWithDatesAfterEnd: 0,
|
||||
assignmentsWithOraDatesAfterEnd: ['test'],
|
||||
assignmentsWithOraDatesBeforeStart: 0,
|
||||
},
|
||||
{
|
||||
hasStartDate: true,
|
||||
hasEndDate: true,
|
||||
},
|
||||
)).toEqual(false);
|
||||
},
|
||||
);
|
||||
|
||||
describe('hasShortVideoDuration', () => {
|
||||
it('returns true if course run has no videos', () => {
|
||||
expect(validators.hasShortVideoDuration({ totalNumber: 0 })).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns true if course run videos have a median duration <= to 600', () => {
|
||||
expect(validators.hasShortVideoDuration({ totalNumber: 1, durations: { median: 100 } }))
|
||||
.toEqual(true);
|
||||
});
|
||||
|
||||
it('returns true if course run videos have a median duration > to 600', () => {
|
||||
expect(validators.hasShortVideoDuration({ totalNumber: 10, durations: { median: 700 } }))
|
||||
.toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasMobileFriendlyVideos', () => {
|
||||
it('returns true if course run has no videos', () => {
|
||||
expect(validators.hasMobileFriendlyVideos({ totalNumber: 0 })).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns true if course run videos are >= 90% mobile friendly', () => {
|
||||
expect(validators.hasMobileFriendlyVideos({ totalNumber: 10, numMobileEncoded: 9 }))
|
||||
.toEqual(true);
|
||||
});
|
||||
|
||||
it('returns true if course run videos are < 90% mobile friendly', () => {
|
||||
expect(validators.hasMobileFriendlyVideos({ totalNumber: 10, numMobileEncoded: 8 }))
|
||||
.toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasDiverseSequences', () => {
|
||||
it('returns true if < 20% of visible subsections have more than one block type', () => {
|
||||
expect(validators.hasDiverseSequences({ totalVisible: 10, numWithOneBlockType: 1 }))
|
||||
.toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false if no visible subsections', () => {
|
||||
expect(validators.hasDiverseSequences({ totalVisible: 0 })).toEqual(false);
|
||||
});
|
||||
|
||||
it('returns false if >= 20% of visible subsections have more than one block type', () => {
|
||||
expect(validators.hasDiverseSequences({ totalVisible: 10, numWithOneBlockType: 3 }))
|
||||
.toEqual(false);
|
||||
});
|
||||
|
||||
it('return false if < 0 visible subsections', () => {
|
||||
expect(validators.hasDiverseSequences({ totalVisible: -1, numWithOneBlockType: 1 }))
|
||||
.toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasWeeklyHighlights', () => {
|
||||
it('returns true when course run has highlights enabled', () => {
|
||||
const data = { highlightsActiveForCourse: true, highlightsEnabled: true };
|
||||
expect(validators.hasWeeklyHighlights(data)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when course run has highlights enabled', () => {
|
||||
const data = { highlightsActiveForCourse: false, highlightsEnabled: false };
|
||||
expect(validators.hasWeeklyHighlights(data)).toEqual(false);
|
||||
|
||||
data.highlightsEnabled = true;
|
||||
data.highlightsActiveForCourse = false;
|
||||
expect(validators.hasWeeklyHighlights(data)).toEqual(false);
|
||||
|
||||
data.highlightsEnabled = false;
|
||||
data.highlightsActiveForCourse = true;
|
||||
expect(validators.hasWeeklyHighlights(data)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasShortUnitDepth', () => {
|
||||
it('returns true when course run has median number of blocks <= 3', () => {
|
||||
const units = {
|
||||
numBlocks: {
|
||||
median: 3,
|
||||
},
|
||||
};
|
||||
|
||||
expect(validators.hasShortUnitDepth(units)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when course run has median number of blocks > 3', () => {
|
||||
const units = {
|
||||
numBlocks: {
|
||||
median: 4,
|
||||
},
|
||||
};
|
||||
|
||||
expect(validators.hasShortUnitDepth(units)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasProctoringEscalationEmail', () => {
|
||||
it('returns true when the course has a proctoring escalation email', () => {
|
||||
const proctoring = { hasProctoringEscalationEmail: true };
|
||||
expect(validators.hasProctoringEscalationEmail(proctoring)).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns false when the course does not have a proctoring escalation email', () => {
|
||||
const proctoring = { hasProctoringEscalationEmail: false };
|
||||
expect(validators.hasProctoringEscalationEmail(proctoring)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
79
src/course-outline/utils/getChecklistForStatusBar.js
Normal file
79
src/course-outline/utils/getChecklistForStatusBar.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { LAUNCH_CHECKLIST, BEST_PRACTICES_CHECKLIST } from '../constants';
|
||||
import { getChecklistValues, getChecklistValidatedValue } from './getChecklistValues';
|
||||
|
||||
/**
|
||||
* Get status bar course launch checklist values
|
||||
* @param {object} data - course launch data
|
||||
* @returns {
|
||||
* totalCourseLaunchChecks: {number},
|
||||
* completedCourseLaunchChecks: {number}
|
||||
* } - total and completed launch checklist items
|
||||
*/
|
||||
const getCourseLaunchChecklist = (data) => {
|
||||
if (Object.keys(data).length > 0) {
|
||||
const { isSelfPaced, certificates } = data;
|
||||
|
||||
const filteredCourseLaunchChecks = getChecklistValues({
|
||||
checklist: LAUNCH_CHECKLIST.data,
|
||||
isSelfPaced,
|
||||
hasCertificatesEnabled: certificates.isEnabled,
|
||||
hasHighlightsEnabled: false,
|
||||
});
|
||||
|
||||
const completedCourseLaunchChecks = filteredCourseLaunchChecks.reduce((result, currentValue) => {
|
||||
const value = getChecklistValidatedValue(data, currentValue.id);
|
||||
return value ? result + 1 : result;
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
totalCourseLaunchChecks: filteredCourseLaunchChecks.length,
|
||||
completedCourseLaunchChecks,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
totalCourseLaunchChecks: 0,
|
||||
completedCourseLaunchChecks: 0,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get status bar course best practices checklist values
|
||||
* @param {object} data - course best practices data
|
||||
* @returns {
|
||||
* totalCourseBestPracticesChecks: {number},
|
||||
* completedCourseBestPracticesChecks: {number}
|
||||
* } - total and completed launch checklist items
|
||||
*/
|
||||
const getCourseBestPracticesChecklist = (data) => {
|
||||
if (Object.keys(data).length > 0) {
|
||||
const { isSelfPaced, sections } = data;
|
||||
|
||||
const filteredBestPracticesChecks = getChecklistValues({
|
||||
checklist: BEST_PRACTICES_CHECKLIST.data,
|
||||
isSelfPaced,
|
||||
hasCertificatesEnabled: false,
|
||||
hasHighlightsEnabled: sections.highlightsEnadled,
|
||||
});
|
||||
|
||||
const completedCourseBestPracticesChecks = filteredBestPracticesChecks.reduce((result, currentValue) => {
|
||||
const value = getChecklistValidatedValue(data, currentValue.id);
|
||||
return value ? result + 1 : result;
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
totalCourseBestPracticesChecks: filteredBestPracticesChecks.length,
|
||||
completedCourseBestPracticesChecks,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
totalCourseBestPracticesChecks: 0,
|
||||
completedCourseBestPracticesChecks: 0,
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
getCourseLaunchChecklist,
|
||||
getCourseBestPracticesChecklist,
|
||||
};
|
||||
79
src/course-outline/utils/getChecklistValues.js
Normal file
79
src/course-outline/utils/getChecklistValues.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { CHECKLIST_FILTERS } from '../constants';
|
||||
import * as healthValidators from './courseChecklistValidators';
|
||||
|
||||
/**
|
||||
* The utilities are taken from the https://github.com/openedx/studio-frontend repository.
|
||||
* Perform a minor refactoring of the functions while preserving their original functionality.
|
||||
*/
|
||||
const getChecklistValidatedValue = (data, id) => {
|
||||
const {
|
||||
updates,
|
||||
grades,
|
||||
certificates,
|
||||
dates,
|
||||
assignments,
|
||||
videos,
|
||||
subsections,
|
||||
sections,
|
||||
units,
|
||||
proctoring,
|
||||
} = data;
|
||||
|
||||
switch (id) {
|
||||
case 'welcomeMessage':
|
||||
return healthValidators.hasWelcomeMessage(updates);
|
||||
case 'gradingPolicy':
|
||||
return healthValidators.hasGradingPolicy(grades);
|
||||
case 'certificate':
|
||||
return healthValidators.hasCertificate(certificates);
|
||||
case 'courseDates':
|
||||
return healthValidators.hasDates(dates);
|
||||
case 'assignmentDeadlines':
|
||||
return healthValidators.hasAssignmentDeadlines(assignments, dates);
|
||||
case 'videoDuration':
|
||||
return healthValidators.hasShortVideoDuration(videos);
|
||||
case 'mobileFriendlyVideo':
|
||||
return healthValidators.hasMobileFriendlyVideos(videos);
|
||||
case 'diverseSequences':
|
||||
return healthValidators.hasDiverseSequences(subsections);
|
||||
case 'weeklyHighlights':
|
||||
return healthValidators.hasWeeklyHighlights(sections);
|
||||
case 'unitDepth':
|
||||
return healthValidators.hasShortUnitDepth(units);
|
||||
case 'proctoringEmail':
|
||||
return healthValidators.hasProctoringEscalationEmail(proctoring);
|
||||
default:
|
||||
throw new Error(`Unknown validator ${id}.`);
|
||||
}
|
||||
};
|
||||
|
||||
const getChecklistValues = ({
|
||||
checklist,
|
||||
isSelfPaced,
|
||||
hasCertificatesEnabled,
|
||||
hasHighlightsEnabled,
|
||||
needsProctoringEscalationEmail,
|
||||
}) => {
|
||||
let filteredCheckList;
|
||||
|
||||
if (isSelfPaced) {
|
||||
filteredCheckList = checklist.filter(({ pacingTypeFilter }) => pacingTypeFilter === CHECKLIST_FILTERS.ALL
|
||||
|| pacingTypeFilter === CHECKLIST_FILTERS.SELF_PACED);
|
||||
} else {
|
||||
filteredCheckList = checklist.filter(({ pacingTypeFilter }) => pacingTypeFilter === CHECKLIST_FILTERS.ALL
|
||||
|| pacingTypeFilter === CHECKLIST_FILTERS.INSTRUCTOR_PACED);
|
||||
}
|
||||
|
||||
filteredCheckList = filteredCheckList.filter(({ id }) => id !== 'certificate'
|
||||
|| hasCertificatesEnabled);
|
||||
|
||||
filteredCheckList = filteredCheckList.filter(({ id }) => id !== 'weeklyHighlights'
|
||||
|| hasHighlightsEnabled);
|
||||
|
||||
filteredCheckList = filteredCheckList.filter(({ id }) => id !== 'proctoringEmail'
|
||||
|| needsProctoringEscalationEmail);
|
||||
|
||||
return filteredCheckList;
|
||||
};
|
||||
|
||||
export { getChecklistValues, getChecklistValidatedValue };
|
||||
86
src/course-outline/utils/getChecklistValues.test.js
Normal file
86
src/course-outline/utils/getChecklistValues.test.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { getChecklistValues } from './getChecklistValues';
|
||||
import { CHECKLIST_FILTERS } from '../constants';
|
||||
|
||||
const checklist = [
|
||||
{
|
||||
id: 'welcomeMessage',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'gradingPolicy',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'certificate',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'courseDates',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
{
|
||||
id: 'assignmentDeadlines',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.INSTRUCTOR_PACED,
|
||||
},
|
||||
{
|
||||
id: 'weeklyHighlights',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.SELF_PACED,
|
||||
},
|
||||
{
|
||||
id: 'proctoringEmail',
|
||||
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
|
||||
},
|
||||
];
|
||||
|
||||
let courseData;
|
||||
describe('getChecklistValues utility function', () => {
|
||||
beforeEach(() => {
|
||||
courseData = {
|
||||
isSelfPaced: true,
|
||||
hasCertificatesEnabled: true,
|
||||
hasHighlightsEnabled: true,
|
||||
needsProctoringEscalationEmail: true,
|
||||
};
|
||||
});
|
||||
it('returns only checklist items with filters ALL and SELF_PACED when isSelfPaced is true', () => {
|
||||
const filteredChecklist = getChecklistValues({ checklist, ...courseData });
|
||||
|
||||
filteredChecklist.forEach(((
|
||||
item => expect(item.pacingTypeFilter === CHECKLIST_FILTERS.ALL
|
||||
|| item.pacingTypeFilter === CHECKLIST_FILTERS.SELF_PACED)
|
||||
)));
|
||||
|
||||
expect(filteredChecklist.filter(item => item.pacingTypeFilter === CHECKLIST_FILTERS.ALL).length)
|
||||
.toEqual(checklist.filter(item => item.pacingTypeFilter === CHECKLIST_FILTERS.ALL).length);
|
||||
expect(filteredChecklist.filter(item => item.pacingTypeFilter === CHECKLIST_FILTERS.SELF_PACED).length)
|
||||
.toEqual(checklist.filter(item => item.pacingTypeFilter === CHECKLIST_FILTERS.SELF_PACED).length);
|
||||
});
|
||||
|
||||
it('returns only checklist items with filters ALL and INSTRUCTOR_PACED when isSelfPaced is false', () => {
|
||||
courseData.isSelfPaced = false;
|
||||
const filteredChecklist = getChecklistValues({ checklist, ...courseData });
|
||||
|
||||
filteredChecklist.forEach(((
|
||||
item => expect(item.pacingTypeFilter === CHECKLIST_FILTERS.ALL
|
||||
|| item.pacingTypeFilter === CHECKLIST_FILTERS.INSTRUCTOR_PACED)
|
||||
)));
|
||||
|
||||
expect(filteredChecklist.filter(item => item.pacingTypeFilter === CHECKLIST_FILTERS.ALL).length)
|
||||
.toEqual(checklist.filter(item => item.pacingTypeFilter === CHECKLIST_FILTERS.ALL).length);
|
||||
expect(filteredChecklist
|
||||
.filter(item => item.pacingTypeFilter === CHECKLIST_FILTERS.INSTRUCTOR_PACED).length)
|
||||
.toEqual(checklist.filter(item => item.pacingTypeFilter === CHECKLIST_FILTERS.INSTRUCTOR_PACED).length);
|
||||
});
|
||||
|
||||
it('excludes weekly highlights when they are disabled', () => {
|
||||
courseData.hasHighlightsEnabled = false;
|
||||
const filteredChecklist = getChecklistValues({ checklist, ...courseData });
|
||||
expect(filteredChecklist.filter(item => item.id === 'weeklyHighlights').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('excludes proctoring escalation email when not needed', () => {
|
||||
courseData.needsProctoringEscalationEmail = false;
|
||||
const filteredChecklist = getChecklistValues({ checklist, ...courseData });
|
||||
expect(filteredChecklist.filter(item => item.id === 'proctoringEmail').length).toEqual(0);
|
||||
});
|
||||
});
|
||||
35
src/help-urls/__mocks__/helpUrls.js
Normal file
35
src/help-urls/__mocks__/helpUrls.js
Normal file
@@ -0,0 +1,35 @@
|
||||
module.exports = {
|
||||
default: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/index.html',
|
||||
home: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/getting_started/CA_get_started_Studio.html',
|
||||
develop_course: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/index.html',
|
||||
outline: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_outline.html',
|
||||
unit: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_units.html',
|
||||
visibility: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/controlling_content_visibility.html',
|
||||
updates: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/handouts_updates.html',
|
||||
pages: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/pages.html',
|
||||
files: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/course_files.html',
|
||||
textbooks: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/textbooks.html',
|
||||
schedule: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/set_up_course/studio_add_course_information/index.html',
|
||||
grading: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/grading/index.html',
|
||||
team_course: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/set_up_course/studio_add_course_information/studio_course_staffing.html',
|
||||
team_library: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_components/libraries.html#give-other-users-access-to-your-library',
|
||||
advanced: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/index.html',
|
||||
checklist: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/set_up_course/index.html',
|
||||
import_library: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_components/libraries.html#import-a-library',
|
||||
import_course: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/releasing_course/export_import_course.html#import-a-course',
|
||||
export_library: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_components/libraries.html#export-a-library',
|
||||
export_course: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/releasing_course/export_import_course.html#export-a-course',
|
||||
welcome: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/getting_started/index.html',
|
||||
login: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/getting_started/index.html',
|
||||
register: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/getting_started/index.html',
|
||||
content_libraries: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_components/libraries.html',
|
||||
content_groups: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_features/cohorts/cohorted_courseware.html',
|
||||
enrollment_tracks: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_features/diff_content/enroll_track_courseware.html',
|
||||
group_configurations: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_features/content_experiments/content_experiments_configure.html#set-up-group-configurations-in-edx-studio',
|
||||
container: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_components.html#components-that-contain-other-components',
|
||||
video: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/video/index.html',
|
||||
certificates: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/set_up_course/studio_add_course_information/studio_creating_certificates.html',
|
||||
content_highlights: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_sections.html#set-section-highlights-for-weekly-course-highlight-messages',
|
||||
image_accessibility: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/accessibility/best_practices_course_content_dev.html#use-best-practices-for-describing-images',
|
||||
social_sharing: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/social_sharing.html',
|
||||
};
|
||||
2
src/help-urls/__mocks__/index.js
Normal file
2
src/help-urls/__mocks__/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export { default as helpUrls } from './helpUrls';
|
||||
@@ -2,8 +2,10 @@
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
export const getHelpUrlsApiUrl = () => `${getConfig().STUDIO_BASE_URL}/api/contentstore/v1/help_urls`;
|
||||
|
||||
export async function getHelpUrls() {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(`${getConfig().STUDIO_BASE_URL}/api/contentstore/v1/help_urls`);
|
||||
.get(getHelpUrlsApiUrl());
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
18
src/hooks.js
Normal file
18
src/hooks.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useEffect } from 'react';
|
||||
import { history } from '@edx/frontend-platform';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const useScrollToHashElement = ({ isLoading }) => {
|
||||
useEffect(() => {
|
||||
const currentHash = window.location.hash;
|
||||
|
||||
if (currentHash) {
|
||||
const element = document.querySelector(currentHash);
|
||||
|
||||
if (element) {
|
||||
element.scrollIntoView();
|
||||
history.replace({ hash: '' });
|
||||
}
|
||||
}
|
||||
}, [isLoading]);
|
||||
};
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "¡Ya casi terminamos! Para completar su registro, necesitamos que verifique su dirección de correo electrónico ({email}). Un mensaje de activación y los pasos a seguir le estarán esperando allí.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "¿Necesita ayuda?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Por favor revise su correo no desado en caso de que nuestro correo no esté en su buzón de entrada. ¿Aún no encuentra el correo de verificación? Pida ayuda a través del vínculo siguiente."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Presque là! Afin de finaliser votre inscription, nous avons besoin que vous vérifiiez votre adresse courriel ({email}). Un message d’activation et les prochaines étapes devraient vous y attendre.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Besoin d'aide?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Merci de vérifier votre corbeille ou votre dossier de pourriel au cas où notre courriel ne se trouve pas dans votre boite de réception. Vous ne trouvez toujours pas le courriel de vérification? Demandez de l'aide via le lien ci-dessous."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,4 @@
|
||||
"course-authoring.studio-home.verify-email.banner.description": "Almost there! In order to complete your sign up we need you to verify your email address ({email}). An activation message and next steps should be waiting for you there.",
|
||||
"course-authoring.studio-home.verify-email.sidebar.title": "Need help?",
|
||||
"course-authoring.studio-home.verify-email.sidebar.description": "Please check your Junk or Spam folders in case our email isn't in your INBOX. Still can't find the verification email? Request help via the link below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,3 +21,4 @@
|
||||
@import "taxonomy/taxonomy-card/TaxonomyCard";
|
||||
@import "files-and-videos";
|
||||
@import "content-tags-drawer/TagBubble";
|
||||
@import "course-outline/CourseOutline";
|
||||
|
||||
@@ -16,6 +16,8 @@ import { useModel } from '../generic/model-store';
|
||||
import AlertMessage from '../generic/alert-message';
|
||||
import InternetConnectionAlert from '../generic/internet-connection-alert';
|
||||
import { STATEFUL_BUTTON_STATES } from '../constants';
|
||||
import getPageHeadTitle from '../generic/utils';
|
||||
import { useScrollToHashElement } from '../hooks';
|
||||
import {
|
||||
fetchCourseSettingsQuery,
|
||||
fetchCourseDetailsQuery,
|
||||
@@ -40,7 +42,6 @@ import LicenseSection from './license-section';
|
||||
import ScheduleSidebar from './schedule-sidebar';
|
||||
import messages from './messages';
|
||||
import { useSaveValuesPrompt } from './hooks';
|
||||
import getPageHeadTitle from '../generic/utils';
|
||||
|
||||
const ScheduleAndDetails = ({ intl, courseId }) => {
|
||||
const courseSettings = useSelector(getCourseSettings);
|
||||
@@ -133,6 +134,8 @@ const ScheduleAndDetails = ({ intl, courseId }) => {
|
||||
dispatch(fetchCourseDetailsQuery(courseId));
|
||||
}, [courseId]);
|
||||
|
||||
useScrollToHashElement({ isLoading });
|
||||
|
||||
if (isLoading) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <></>;
|
||||
|
||||
@@ -19,7 +19,7 @@ const EntranceExam = ({
|
||||
const toggleEntranceExam = () => onChange((!showEntranceExam).toString(), 'entranceExamEnabled');
|
||||
const courseOutlineDestination = getPagePath(
|
||||
courseId,
|
||||
process.env.ENABLE_NEW_COURSE_OUTLINE_PAGE,
|
||||
'false',
|
||||
'course',
|
||||
);
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ const ScheduleSection = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="section-container schedule-section">
|
||||
<section className="section-container schedule-section" id="schedule">
|
||||
<SectionSubHeader
|
||||
title={intl.formatMessage(messages.scheduleTitle)}
|
||||
description={intl.formatMessage(messages.scheduleDescription)}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { reducer as courseExportReducer } from './export-page/data/slice';
|
||||
import { reducer as genericReducer } from './generic/data/slice';
|
||||
import { reducer as courseImportReducer } from './import-page/data/slice';
|
||||
import { reducer as videosReducer } from './files-and-videos/videos-page/data/slice';
|
||||
import { reducer as courseOutlineReducer } from './course-outline/data/slice';
|
||||
|
||||
export default function initializeStore(preloadedState = undefined) {
|
||||
return configureStore({
|
||||
@@ -42,6 +43,7 @@ export default function initializeStore(preloadedState = undefined) {
|
||||
generic: genericReducer,
|
||||
courseImport: courseImportReducer,
|
||||
videos: videosReducer,
|
||||
courseOutline: courseOutlineReducer,
|
||||
},
|
||||
preloadedState,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user