Skip to content

Commit

Permalink
feat: Course outline Status Bar (#50)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
2 people authored and navinkarkera committed Nov 22, 2023
1 parent 3968b1a commit e06ce7c
Show file tree
Hide file tree
Showing 21 changed files with 1,024 additions and 42 deletions.
104 changes: 68 additions & 36 deletions src/course-outline/CourseOutline.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,31 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { Container, Layout } from '@edx/paragon';

import SubHeader from '../generic/sub-header/SubHeader';
import { RequestStatus } from '../data/constants';
import InternetConnectionAlert from '../generic/internet-connection-alert';
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,
isSectionsExpanded,
isEnableHighlightsModalOpen,
isInternetConnectionAlertFailed,
headerNavigationsActions,
openEnableHighlightsModal,
closeEnableHighlightsModal,
handleEnableHighlightsSubmit,
handleInternetConnectionFailed,
} = useCourseOutline({ courseId });

if (isLoading) {
Expand All @@ -25,43 +37,63 @@ const CourseOutline = ({ courseId }) => {
}

return (
<Container size="xl" className="m-4">
<section className="course-outline-container mb-4 mt-5">
<SubHeader
className="mt-5"
title={intl.formatMessage(messages.headingTitle)}
subtitle={intl.formatMessage(messages.headingSubtitle)}
withSubHeaderContent={false}
headerActions={(
<HeaderNavigations
isReIndexShow={isReIndexShow}
isSectionsExpanded={isSectionsExpanded}
headerNavigationsActions={headerNavigationsActions}
/>
)}
<>
<Container size="xl" className="m-4">
<section className="course-outline-container mb-4 mt-5">
<SubHeader
className="mt-5"
title={intl.formatMessage(messages.headingTitle)}
subtitle={intl.formatMessage(messages.headingSubtitle)}
withSubHeaderContent={false}
headerActions={(
<HeaderNavigations
isReIndexShow={isReIndexShow}
isSectionsExpanded={isSectionsExpanded}
headerNavigationsActions={headerNavigationsActions}
/>
)}
/>
<Layout
lg={[{ span: 9 }, { span: 3 }]}
md={[{ span: 9 }, { span: 3 }]}
sm={[{ span: 9 }, { span: 3 }]}
xs={[{ span: 9 }, { span: 3 }]}
xl={[{ span: 9 }, { span: 3 }]}
>
<Layout.Element>
<article>
<div>
<section className="course-outline-section">
<StatusBar
courseId={courseId}
isLaoding={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}
/>
<Layout
lg={[{ span: 9 }, { span: 3 }]}
md={[{ span: 9 }, { span: 3 }]}
sm={[{ span: 9 }, { span: 3 }]}
xs={[{ span: 9 }, { span: 3 }]}
xl={[{ span: 9 }, { span: 3 }]}
>
<Layout.Element>
<article>
<div>
<section className="course-outline-section">
{/* TODO add status bar and list of outlines */}
</section>
</div>
</article>
</Layout.Element>
<Layout.Element>
<OutlineSideBar courseId={courseId} />
</Layout.Element>
</Layout>
</section>
</Container>
</div>
</>
);
};

Expand Down
1 change: 1 addition & 0 deletions src/course-outline/CourseOutline.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import "./header-navigations/HeaderNavigations";
@import "./outline-sidebar/OulineSidebar";
@import "./status-bar/StatusBar";
59 changes: 59 additions & 0 deletions src/course-outline/constants.js
Original file line number Diff line number Diff line change
@@ -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,
},
],
};
72 changes: 72 additions & 0 deletions src/course-outline/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@ 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}`;
const getEnableHighlightsEmailsApiUrl = (courseId) => {
const formattedCourseId = courseId.split('course-v1:')[1];
return `${getApiBaseUrl()}/xblock/block-v1:${formattedCourseId}+type@course+block@course`;
};

/**
* Get course outline index.
Expand All @@ -15,3 +30,60 @@ export async function getCourseOutlineIndex(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;
}
2 changes: 2 additions & 0 deletions src/course-outline/data/selectors.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export const getOutlineIndexData = (state) => state.courseOutline.outlineIndexData;
export const getLoadingOutlineIndexStatus = (state) => state.courseOutline.loadingOutlineIndexStatus;
export const getStatusBarData = (state) => state.courseOutline.statusBarData;
export const getSavingStatus = (state) => state.courseOutline.savingStatus;
35 changes: 35 additions & 0 deletions src/course-outline/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ const slice = createSlice({
initialState: {
loadingOutlineIndexStatus: 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 }) => {
Expand All @@ -16,12 +29,34 @@ const slice = createSlice({
updateLoadingOutlineIndexStatus: (state, { payload }) => {
state.loadingOutlineIndexStatus = 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,
updateLoadingOutlineIndexStatus,
updateStatusBar,
fetchStatusBarChecklistSuccess,
fetchStatusBarSelPacedSuccess,
updateSavingStatus,
} = slice.actions;

export const {
Expand Down
Loading

0 comments on commit e06ce7c

Please sign in to comment.