Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: optional xblocks #1296

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/course-home/data/__factories__/progressTabData.factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ Factory.define('progressTabData')
incomplete_count: 1,
locked_count: 0,
},
optional_completion_summary: {
complete_count: 1,
incomplete_count: 1,
locked_count: 0,
},
course_grade: {
letter_grade: 'pass',
percent: 1,
Expand Down
7 changes: 7 additions & 0 deletions src/course-home/data/__snapshots__/redux.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ Object {
"complete": false,
"courseId": "course-v1:edX+DemoX+Demo_Course",
"id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
"optional": undefined,
"resumeBlock": false,
"sequenceIds": Array [
"block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
Expand All @@ -444,6 +445,7 @@ Object {
"effortTime": 15,
"icon": null,
"id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
"optional": undefined,
"sectionId": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
"showLink": true,
"title": "Title of Sequence",
Expand Down Expand Up @@ -636,6 +638,11 @@ Object {
},
"hasScheduledContent": false,
"id": "course-v1:edX+DemoX+Demo_Course",
"optionalCompletionSummary": Object {
"completeCount": 1,
"incompleteCount": 1,
"lockedCount": 0,
},
"sectionScores": Array [
Object {
"displayName": "First section",
Expand Down
2 changes: 2 additions & 0 deletions src/course-home/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export function normalizeOutlineBlocks(courseId, blocks) {
title: block.display_name,
resumeBlock: block.resume_block,
sequenceIds: block.children || [],
optional: block.optional_content,
};
break;

Expand All @@ -152,6 +153,7 @@ export function normalizeOutlineBlocks(courseId, blocks) {
// link in the outline (even though we ignore the given url and use an internal <Link> to ourselves).
showLink: !!block.lms_web_url,
title: block.display_name,
optional: block.optional_content,
};
break;

Expand Down
4 changes: 3 additions & 1 deletion src/course-home/outline-tab/Section.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Collapsible, IconButton } from '@edx/paragon';
import { Badge, Collapsible, IconButton } from '@edx/paragon';
import { faCheckCircle as fasCheckCircle, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
import { faCheckCircle as farCheckCircle } from '@fortawesome/free-regular-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
Expand All @@ -23,6 +23,7 @@ const Section = ({
complete,
sequenceIds,
title,
optional,
} = section;
const {
courseBlocks: {
Expand Down Expand Up @@ -64,6 +65,7 @@ const Section = ({
</div>
<div className="col-10 ml-3 p-0 font-weight-bold text-dark-500">
<span className="align-middle">{title}</span>
<Badge className="ml-2" variant="light" hidden={!optional}>{intl.formatMessage(messages.optionalContent)}</Badge>
<span className="sr-only">
, {intl.formatMessage(complete ? messages.completedSection : messages.incompleteSection)}
</span>
Expand Down
4 changes: 4 additions & 0 deletions src/course-home/outline-tab/SequenceLink.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const SequenceLink = ({
due,
showLink,
title,
optional,
} = sequence;
const {
userTimezone,
Expand Down Expand Up @@ -115,6 +116,9 @@ const SequenceLink = ({
</div>
</div>
<div className="row w-100 m-0 ml-3 pl-3">
<small className="text-body pl-2 pr-0">
{optional ? intl.formatMessage(messages.optionalContent) : ''}
</small>
<small className="text-body pl-2">
{due ? dueDateMessage : noDueDateMessage}
</small>
Expand Down
5 changes: 5 additions & 0 deletions src/course-home/outline-tab/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ const messages = defineMessages({
defaultMessage: 'Open',
description: 'A button to open the given section of the course outline',
},
optionalContent: {
id: 'learning.outline.optionalBlock',
defaultMessage: 'Optional',
description: 'Used as a label to indicate that a section, sequence, or unit is optional.',
},
proctoringInfoPanel: {
id: 'learning.proctoringPanel.header',
defaultMessage: 'This course contains proctored exams',
Expand Down
20 changes: 20 additions & 0 deletions src/course-home/progress-tab/ProgressTab.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ describe('Progress Tab', () => {
incomplete_count: 1,
locked_count: 1,
},
optional_completion_summary: {
complete_count: 1,
incomplete_count: 1,
locked_count: 0,
},
verified_mode: {
access_expiration_date: '2050-01-01T12:00:00',
currency: 'USD',
Expand Down Expand Up @@ -304,6 +309,11 @@ describe('Progress Tab', () => {
incomplete_count: 1,
locked_count: 1,
},
optional_completion_summary: {
complete_count: 1,
incomplete_count: 1,
locked_count: 0,
},
verified_mode: {
access_expiration_date: '2050-01-01T12:00:00',
currency: 'USD',
Expand Down Expand Up @@ -364,6 +374,11 @@ describe('Progress Tab', () => {
incomplete_count: 1,
locked_count: 1,
},
optional_completion_summary: {
complete_count: 1,
incomplete_count: 1,
locked_count: 0,
},
section_scores: [
{
display_name: 'First section',
Expand Down Expand Up @@ -402,6 +417,11 @@ describe('Progress Tab', () => {
incomplete_count: 1,
locked_count: 1,
},
optional_completion_summary: {
complete_count: 1,
incomplete_count: 1,
locked_count: 0,
},
verified_mode: {
access_expiration_date: '2050-01-01T12:00:00',
currency: 'USD',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { useModel } from '../../../generic/model-store';

import CompleteDonutSegment from './CompleteDonutSegment';
import IncompleteDonutSegment from './IncompleteDonutSegment';
import LockedDonutSegment from './LockedDonutSegment';
import messages from './messages';

const CompletionDonutChart = ({ intl }) => {
const CompletionDonutChart = ({ intl, optional }) => {
const {
courseId,
} = useSelector(state => state.courseHome);

const key = optional ? 'optionalCompletionSummary' : 'completionSummary';
const label = optional ? intl.formatMessage(messages.optionalDonutLabel) : intl.formatMessage(messages.donutLabel);

const progress = useModel('progress', courseId);
const {
completionSummary: {
completeCount,
incompleteCount,
lockedCount,
},
} = useModel('progress', courseId);
completeCount,
incompleteCount,
lockedCount,
} = progress[key];

const numTotalUnits = completeCount + incompleteCount + lockedCount;
const completePercentage = completeCount ? Number(((completeCount / numTotalUnits) * 100).toFixed(0)) : 0;
Expand All @@ -30,6 +33,10 @@

const isLocaleRtl = isRtl(getLocale());

if (optional && numTotalUnits === 0) {
return <></>;

Check warning on line 37 in src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx

View check run for this annotation

Codecov / codecov/patch

src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx#L37

Added line #L37 was not covered by tests
}

return (
<>
<svg role="img" width="50%" height="100%" viewBox="0 0 42 42" className="donut" style={{ maxWidth: '178px' }} aria-hidden="true">
Expand All @@ -42,7 +49,7 @@
{completePercentage}{isLocaleRtl && '\u200f'}%
</text>
<text x="50%" y="50%" className="donut-chart-label">
{intl.formatMessage(messages.donutLabel)}
{label}
</text>
</g>
<IncompleteDonutSegment incompletePercentage={incompletePercentage} />
Expand All @@ -62,8 +69,13 @@
);
};

CompletionDonutChart.defaultProps = {
optional: false,
};

CompletionDonutChart.propTypes = {
intl: intlShape.isRequired,
optional: PropTypes.bool,
};

export default injectIntl(CompletionDonutChart);
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const CourseCompletion = ({ intl }) => (
</p>
</div>
<div className="col-12 col-sm-6 col-md-5 mt-sm-n3 p-0 text-center">
<CompletionDonutChart />
<CompletionDonutChart optional={false} />
<CompletionDonutChart optional />
</div>
</div>
</section>
Expand Down
5 changes: 5 additions & 0 deletions src/course-home/progress-tab/course-completion/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ const messages = defineMessages({
defaultMessage: 'completed',
description: 'Label text for progress donut chart',
},
optionalDonutLabel: {
id: 'progress.completion.optionalDonut.label',
defaultMessage: 'optional',
description: 'Label text for optional progress donut chart',
},
completionBody: {
id: 'progress.completion.body',
defaultMessage: 'This represents how much of the course content you have completed. Note that some content may not yet be released.',
Expand Down
3 changes: 2 additions & 1 deletion src/courseware/course/sequence/Unit.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { AppContext, ErrorPage } from '@edx/frontend-platform/react';
import { Modal } from '@edx/paragon';
import { Modal, Badge } from '@edx/paragon';
import PropTypes from 'prop-types';
import React, {
Suspense, useCallback, useContext, useEffect, useLayoutEffect, useState,
Expand Down Expand Up @@ -155,6 +155,7 @@ const Unit = ({
return (
<div className="unit">
<h1 className="mb-0 h3">{unit.title}</h1>
<Badge className="ml-2" variant="light" hidden={!unit.optional}>{intl.formatMessage(messages.optionalContent)}</Badge>
<h2 className="sr-only">{intl.formatMessage(messages.headerPlaceholder)}</h2>
<BookmarkButton
unitId={unit.id}
Expand Down
5 changes: 5 additions & 0 deletions src/courseware/course/sequence/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const messages = defineMessages({
defaultMessage: 'There is no content here.',
description: 'Message shown when there is no content to show a user inside a learning sequence.',
},
optionalContent: {
id: 'learn.sequence.optionalBlock',
defaultMessage: 'Optional',
description: 'Used as a label to indicate that a section, sequence, or unit is optional.',
},
});

export default messages;
1 change: 1 addition & 0 deletions src/courseware/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ function normalizeSequenceMetadata(sequence) {
contentType: unit.type,
graded: unit.graded,
containsContentTypeGatedContent: unit.contains_content_type_gated_content,
optional: unit.optional_content,
})),
};
}
Expand Down
Loading