Skip to content

Commit f21c855

Browse files
committed
feat: optional xblocks
1 parent 5d8e17f commit f21c855

File tree

13 files changed

+81
-11
lines changed

13 files changed

+81
-11
lines changed

src/course-home/data/__factories__/progressTabData.factory.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ Factory.define('progressTabData')
1212
incomplete_count: 1,
1313
locked_count: 0,
1414
},
15+
optional_completion_summary: {
16+
complete_count: 1,
17+
incomplete_count: 1,
18+
locked_count: 0,
19+
},
1520
course_grade: {
1621
letter_grade: 'pass',
1722
percent: 1,

src/course-home/data/__snapshots__/redux.test.js.snap

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ Object {
428428
"complete": false,
429429
"courseId": "course-v1:edX+DemoX+Demo_Course",
430430
"id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
431+
"optional": undefined,
431432
"resumeBlock": false,
432433
"sequenceIds": Array [
433434
"block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
@@ -444,6 +445,7 @@ Object {
444445
"effortTime": 15,
445446
"icon": null,
446447
"id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
448+
"optional": undefined,
447449
"sectionId": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
448450
"showLink": true,
449451
"title": "Title of Sequence",
@@ -636,6 +638,11 @@ Object {
636638
},
637639
"hasScheduledContent": false,
638640
"id": "course-v1:edX+DemoX+Demo_Course",
641+
"optionalCompletionSummary": Object {
642+
"completeCount": 1,
643+
"incompleteCount": 1,
644+
"lockedCount": 0,
645+
},
639646
"sectionScores": Array [
640647
Object {
641648
"displayName": "First section",

src/course-home/data/api.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export function normalizeOutlineBlocks(courseId, blocks) {
136136
title: block.display_name,
137137
resumeBlock: block.resume_block,
138138
sequenceIds: block.children || [],
139+
optional: block.optional_content,
139140
};
140141
break;
141142

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

src/course-home/outline-tab/Section.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
33
import classNames from 'classnames';
44
import { useLocation } from 'react-router-dom';
55
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
6-
import { Collapsible, IconButton } from '@edx/paragon';
6+
import { Badge, Collapsible, IconButton } from '@edx/paragon';
77
import { faCheckCircle as fasCheckCircle, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
88
import { faCheckCircle as farCheckCircle } from '@fortawesome/free-regular-svg-icons';
99
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -27,6 +27,7 @@ const Section = ({
2727
complete,
2828
sequenceIds,
2929
title,
30+
optional,
3031
} = section;
3132
const {
3233
courseBlocks: {
@@ -74,6 +75,7 @@ const Section = ({
7475
</div>
7576
<div className="col-10 ml-3 p-0 font-weight-bold text-dark-500">
7677
<span className="align-middle">{title}</span>
78+
<Badge className="ml-2" variant="light" hidden={!optional}>{intl.formatMessage(messages.optionalContent)}</Badge>
7779
<span className="sr-only">
7880
, {intl.formatMessage(complete ? messages.completedSection : messages.incompleteSection)}
7981
</span>

src/course-home/outline-tab/SequenceLink.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const SequenceLink = ({
3333
due,
3434
showLink,
3535
title,
36+
optional,
3637
} = sequence;
3738
const {
3839
userTimezone,
@@ -131,6 +132,9 @@ const SequenceLink = ({
131132
</div>
132133
</div>
133134
<div className="row w-100 m-0 ml-3 pl-3">
135+
<small className="text-body pl-2 pr-0">
136+
{optional ? intl.formatMessage(messages.optionalContent) : ''}
137+
</small>
134138
<small className="text-body pl-2">
135139
{due ? dueDateMessage : noDueDateMessage}
136140
</small>

src/course-home/outline-tab/messages.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ const messages = defineMessages({
9999
defaultMessage: 'Open',
100100
description: 'A button to open the given section of the course outline',
101101
},
102+
optionalContent: {
103+
id: 'learning.outline.optionalBlock',
104+
defaultMessage: 'Optional',
105+
description: 'Used as a label to indicate that a section, sequence, or unit is optional.',
106+
},
102107
proctoringInfoPanel: {
103108
id: 'learning.proctoringPanel.header',
104109
defaultMessage: 'This course contains proctored exams',

src/course-home/progress-tab/ProgressTab.test.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@ describe('Progress Tab', () => {
262262
incomplete_count: 1,
263263
locked_count: 1,
264264
},
265+
optional_completion_summary: {
266+
complete_count: 1,
267+
incomplete_count: 1,
268+
locked_count: 0,
269+
},
265270
verified_mode: {
266271
access_expiration_date: '2050-01-01T12:00:00',
267272
currency: 'USD',
@@ -304,6 +309,11 @@ describe('Progress Tab', () => {
304309
incomplete_count: 1,
305310
locked_count: 1,
306311
},
312+
optional_completion_summary: {
313+
complete_count: 1,
314+
incomplete_count: 1,
315+
locked_count: 0,
316+
},
307317
verified_mode: {
308318
access_expiration_date: '2050-01-01T12:00:00',
309319
currency: 'USD',
@@ -364,6 +374,11 @@ describe('Progress Tab', () => {
364374
incomplete_count: 1,
365375
locked_count: 1,
366376
},
377+
optional_completion_summary: {
378+
complete_count: 1,
379+
incomplete_count: 1,
380+
locked_count: 0,
381+
},
367382
section_scores: [
368383
{
369384
display_name: 'First section',
@@ -402,6 +417,11 @@ describe('Progress Tab', () => {
402417
incomplete_count: 1,
403418
locked_count: 1,
404419
},
420+
optional_completion_summary: {
421+
complete_count: 1,
422+
incomplete_count: 1,
423+
locked_count: 0,
424+
},
405425
verified_mode: {
406426
access_expiration_date: '2050-01-01T12:00:00',
407427
currency: 'USD',

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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,28 @@ import { useSelector } from 'react-redux';
33
import {
44
getLocale, injectIntl, intlShape, isRtl,
55
} from '@edx/frontend-platform/i18n';
6+
import PropTypes from 'prop-types';
67
import { useModel } from '../../../generic/model-store';
78

89
import CompleteDonutSegment from './CompleteDonutSegment';
910
import IncompleteDonutSegment from './IncompleteDonutSegment';
1011
import LockedDonutSegment from './LockedDonutSegment';
1112
import messages from './messages';
1213

13-
const CompletionDonutChart = ({ intl }) => {
14+
const CompletionDonutChart = ({ intl, optional }) => {
1415
const {
1516
courseId,
1617
} = useSelector(state => state.courseHome);
1718

19+
const key = optional ? 'optionalCompletionSummary' : 'completionSummary';
20+
const label = optional ? intl.formatMessage(messages.optionalDonutLabel) : intl.formatMessage(messages.donutLabel);
21+
22+
const progress = useModel('progress', courseId);
1823
const {
19-
completionSummary: {
20-
completeCount,
21-
incompleteCount,
22-
lockedCount,
23-
},
24-
} = useModel('progress', courseId);
24+
completeCount,
25+
incompleteCount,
26+
lockedCount,
27+
} = progress[key];
2528

2629
const numTotalUnits = completeCount + incompleteCount + lockedCount;
2730
const completePercentage = completeCount ? Number(((completeCount / numTotalUnits) * 100).toFixed(0)) : 0;
@@ -30,6 +33,10 @@ const CompletionDonutChart = ({ intl }) => {
3033

3134
const isLocaleRtl = isRtl(getLocale());
3235

36+
if (optional && numTotalUnits === 0) {
37+
return <></>;
38+
}
39+
3340
return (
3441
<>
3542
<svg role="img" width="50%" height="100%" viewBox="0 0 42 42" className="donut" style={{ maxWidth: '178px' }} aria-hidden="true">
@@ -42,7 +49,7 @@ const CompletionDonutChart = ({ intl }) => {
4249
{completePercentage}{isLocaleRtl && '\u200f'}%
4350
</text>
4451
<text x="50%" y="50%" className="donut-chart-label">
45-
{intl.formatMessage(messages.donutLabel)}
52+
{label}
4653
</text>
4754
</g>
4855
<IncompleteDonutSegment incompletePercentage={incompletePercentage} />
@@ -62,8 +69,13 @@ const CompletionDonutChart = ({ intl }) => {
6269
);
6370
};
6471

72+
CompletionDonutChart.defaultProps = {
73+
optional: false,
74+
};
75+
6576
CompletionDonutChart.propTypes = {
6677
intl: intlShape.isRequired,
78+
optional: PropTypes.bool,
6779
};
6880

6981
export default injectIntl(CompletionDonutChart);

src/course-home/progress-tab/course-completion/CourseCompletion.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const CourseCompletion = ({ intl }) => (
1414
</p>
1515
</div>
1616
<div className="col-12 col-sm-6 col-md-5 mt-sm-n3 p-0 text-center">
17-
<CompletionDonutChart />
17+
<CompletionDonutChart optional={false} />
18+
<CompletionDonutChart optional />
1819
</div>
1920
</div>
2021
</section>

src/course-home/progress-tab/course-completion/messages.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ const messages = defineMessages({
66
defaultMessage: 'completed',
77
description: 'Label text for progress donut chart',
88
},
9+
optionalDonutLabel: {
10+
id: 'progress.completion.optionalDonut.label',
11+
defaultMessage: 'optional',
12+
description: 'Label text for optional progress donut chart',
13+
},
914
completionBody: {
1015
id: 'progress.completion.body',
1116
defaultMessage: 'This represents how much of the course content you have completed. Note that some content may not yet be released.',

0 commit comments

Comments
 (0)