Skip to content

Commit 15fcb55

Browse files
authored
feat: adds slots for in-context metrics in studio outline and unit views (#1725)
1 parent d1a6af5 commit 15fcb55

27 files changed

Lines changed: 670 additions & 11 deletions

File tree

src/course-outline/CourseOutline.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import AlertMessage from '../generic/alert-message';
3535
import getPageHeadTitle from '../generic/utils';
3636
import { getCurrentItem, getProctoredExamsFlag } from './data/selectors';
3737
import { COURSE_BLOCK_NAMES } from './constants';
38-
import HeaderNavigations from './header-navigations/HeaderNavigations';
3938
import OutlineSideBar from './outline-sidebar/OutlineSidebar';
4039
import StatusBar from './status-bar/StatusBar';
4140
import EnableHighlightsModal from './enable-highlights-modal/EnableHighlightsModal';
@@ -55,6 +54,7 @@ import {
5554
import { useCourseOutline } from './hooks';
5655
import messages from './messages';
5756
import { getTagsExportFile } from './data/api';
57+
import CourseOutlineHeaderActionsSlot from '../plugin-slots/CourseOutlineHeaderActionsSlot';
5858

5959
const CourseOutline = ({ courseId }) => {
6060
const intl = useIntl();
@@ -294,14 +294,15 @@ const CourseOutline = ({ courseId }) => {
294294
title={intl.formatMessage(messages.headingTitle)}
295295
subtitle={intl.formatMessage(messages.headingSubtitle)}
296296
headerActions={(
297-
<HeaderNavigations
297+
<CourseOutlineHeaderActionsSlot
298298
isReIndexShow={isReIndexShow}
299299
isSectionsExpanded={isSectionsExpanded}
300300
headerNavigationsActions={headerNavigationsActions}
301301
isDisabledReindexButton={isDisabledReindexButton}
302302
hasSections={Boolean(sectionsList.length)}
303303
courseActions={courseActions}
304304
errors={errors}
305+
sections={sections}
305306
/>
306307
)}
307308
/>

src/course-outline/CourseOutline.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
@import "./header-navigations/HeaderNavigations";
21
@import "./status-bar/StatusBar";
32
@import "./section-card/SectionCard";
43
@import "./subsection-card/SubsectionCard";

src/course-outline/card-header/CardHeader.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const CardHeader = ({
5454
discussionEnabled,
5555
discussionsSettings,
5656
parentInfo,
57+
extraActionsComponent,
5758
}) => {
5859
const intl = useIntl();
5960
const [searchParams] = useSearchParams();
@@ -145,6 +146,7 @@ const CardHeader = ({
145146
{ getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && !!contentTagCount && (
146147
<TagCount count={contentTagCount} onClick={openManageTagsDrawer} />
147148
)}
149+
{extraActionsComponent}
148150
<Dropdown data-testid={`${namePrefix}-card-header__menu`} onClick={onClickMenuButton}>
149151
<Dropdown.Toggle
150152
className="item-card-header__menu"
@@ -252,6 +254,7 @@ CardHeader.defaultProps = {
252254
discussionsSettings: {},
253255
parentInfo: {},
254256
cardId: '',
257+
extraActionsComponent: null,
255258
};
256259

257260
CardHeader.propTypes = {
@@ -295,6 +298,9 @@ CardHeader.propTypes = {
295298
isTimeLimited: PropTypes.bool,
296299
graded: PropTypes.bool,
297300
}),
301+
// An optional component that is rendered before the dropdown. This is used by the Subsection
302+
// and Unit card components to render their plugin slots.
303+
extraActionsComponent: PropTypes.node,
298304
};
299305

300306
export default CardHeader;

src/course-outline/header-navigations/HeaderNavigations.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const HeaderNavigations = ({
2525
} = headerNavigationsActions;
2626

2727
return (
28-
<nav className="header-navigations ml-auto">
28+
<>
2929
{courseActions.childAddable && (
3030
<OverlayTrigger
3131
placement="bottom"
@@ -90,7 +90,7 @@ const HeaderNavigations = ({
9090
{intl.formatMessage(messages.viewLiveButton)}
9191
</Button>
9292
</OverlayTrigger>
93-
</nav>
93+
</>
9494
);
9595
};
9696

src/course-outline/header-navigations/HeaderNavigations.scss

Lines changed: 0 additions & 4 deletions
This file was deleted.

src/course-outline/subsection-card/SubsectionCard.jsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Add as IconAdd } from '@openedx/paragon/icons';
1111
import classNames from 'classnames';
1212
import { isEmpty } from 'lodash';
1313

14+
import CourseOutlineSubsectionCardExtraActionsSlot from '../../plugin-slots/CourseOutlineSubsectionCardExtraActionsSlot';
1415
import { setCurrentItem, setCurrentSection, setCurrentSubsection } from '../data/slice';
1516
import { RequestStatus } from '../../data/constants';
1617
import CardHeader from '../card-header/CardHeader';
@@ -127,6 +128,13 @@ const SubsectionCard = ({
127128
/>
128129
);
129130

131+
const extraActionsComponent = (
132+
<CourseOutlineSubsectionCardExtraActionsSlot
133+
subsection={subsection}
134+
section={section}
135+
/>
136+
);
137+
130138
useEffect(() => {
131139
if (activeId === id && isExpanded) {
132140
setIsExpanded(false);
@@ -205,6 +213,7 @@ const SubsectionCard = ({
205213
actions={actions}
206214
proctoringExamConfigurationLink={proctoringExamConfigurationLink}
207215
isSequential
216+
extraActionsComponent={extraActionsComponent}
208217
/>
209218
<div className="subsection-card__content item-children" data-testid="subsection-card__content">
210219
<XBlockStatus

src/course-outline/unit-card/UnitCard.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useToggle } from '@openedx/paragon';
66
import { isEmpty } from 'lodash';
77
import { useSearchParams } from 'react-router-dom';
88

9+
import CourseOutlineUnitCardExtraActionsSlot from '../../plugin-slots/CourseOutlineUnitCardExtraActionsSlot';
910
import { setCurrentItem, setCurrentSection, setCurrentSubsection } from '../data/slice';
1011
import { RequestStatus } from '../../data/constants';
1112
import CardHeader from '../card-header/CardHeader';
@@ -111,6 +112,14 @@ const UnitCard = ({
111112
/>
112113
);
113114

115+
const extraActionsComponent = (
116+
<CourseOutlineUnitCardExtraActionsSlot
117+
unit={unit}
118+
subsection={subsection}
119+
section={section}
120+
/>
121+
);
122+
114123
useEffect(() => {
115124
// if this items has been newly added, scroll to it.
116125
// we need to check section.shouldScroll as whole section is fetched when a
@@ -177,6 +186,7 @@ const UnitCard = ({
177186
discussionEnabled={discussionEnabled}
178187
discussionsSettings={discussionsSettings}
179188
parentInfo={parentInfo}
189+
extraActionsComponent={extraActionsComponent}
180190
/>
181191
<div className="unit-card__content item-children" data-testid="unit-card__content">
182192
<XBlockStatus

src/course-unit/CourseUnit.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import Loading from '../generic/Loading';
2525
import AddComponent from './add-component/AddComponent';
2626
import HeaderTitle from './header-title/HeaderTitle';
2727
import Breadcrumbs from './breadcrumbs/Breadcrumbs';
28-
import HeaderNavigations from './header-navigations/HeaderNavigations';
2928
import Sequence from './course-sequence';
3029
import Sidebar from './sidebar';
3130
import SplitTestSidebarInfo from './sidebar/SplitTestSidebarInfo';
@@ -38,6 +37,7 @@ import { PasteNotificationAlert } from './clipboard';
3837
import XBlockContainerIframe from './xblock-container-iframe';
3938
import MoveModal from './move-modal';
4039
import IframePreviewLibraryXBlockChanges from './preview-changes';
40+
import CourseUnitHeaderActionsSlot from '../plugin-slots/CourseUnitHeaderActionsSlot';
4141

4242
const CourseUnit = ({ courseId }) => {
4343
const { blockId } = useParams();
@@ -157,9 +157,11 @@ const CourseUnit = ({ courseId }) => {
157157
/>
158158
)}
159159
headerActions={(
160-
<HeaderNavigations
160+
<CourseUnitHeaderActionsSlot
161161
category={unitCategory}
162162
headerNavigationsActions={headerNavigationsActions}
163+
unitTitle={unitTitle}
164+
verticalBlocks={courseVerticalChildren.children}
163165
/>
164166
)}
165167
/>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# CourseOutlineHeaderActionsSlot
2+
3+
### Slot ID: `course_outline_header_actions_slot`
4+
### Plugin Props:
5+
6+
* `isReIndexShow` - Boolean.
7+
* `isSectionsExpanded` - Boolean.
8+
* `headerNavigationsActions` - Object. See Proptypes definition in the component for details.
9+
* `hasSections` - Boolean. Indicates if the course outline has sections.
10+
* `courseActions` - Object. See Proptypes definition in the component for details.
11+
* `errors` - Object. See Proptypes definition in the component for details.
12+
* `sections` - Array of objects. Sections of the course outline.
13+
14+
## Description
15+
16+
The slot is positioned in the `SubHeader` section of the Course Outline page, suitable for adding action buttons.
17+
18+
The slot by default contains the action buttons such as `+ New Section`, `Reindex`, `View Live`.
19+
20+
## Example
21+
22+
![Screenshot of a custom button in slot](./images/mybutton_added_to_the_slot.png)
23+
24+
The following example configuration inserts an extra button to the header as shown above.
25+
26+
```js
27+
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
28+
import { Button } from '@openedx/paragon';
29+
30+
const MyButton = () => (
31+
<Button>🐣</Button>
32+
);
33+
34+
const config = {
35+
pluginSlots: {
36+
course_outline_header_actions_slot: {
37+
keepDefault: true,
38+
plugins: [
39+
{
40+
op: PLUGIN_OPERATIONS.Insert,
41+
widget: {
42+
id: 'my-extra-button',
43+
priority: 60,
44+
type: DIRECT_PLUGIN,
45+
RenderWidget: MyButton,
46+
},
47+
},
48+
]
49+
}
50+
},
51+
}
52+
53+
export default config;
54+
```
55+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`CourseOutlineHeaderActionsSlot pluginProps are set correctly 1`] = `
4+
<PluginSlot
5+
id="course_outline_header_actions_slot"
6+
pluginProps={
7+
{
8+
"courseActions": {
9+
"childAddable": true,
10+
"deletable": true,
11+
"draggable": true,
12+
"duplicable": true,
13+
},
14+
"errors": {},
15+
"hasSections": false,
16+
"headerNavigationsActions": {
17+
"handleExpandAll": [MockFunction],
18+
"handleNewSection": [MockFunction],
19+
"handleReIndex": [MockFunction],
20+
"lmsLink": "",
21+
},
22+
"isDisabledReindexButton": false,
23+
"isReIndexShow": false,
24+
"isSectionsExpanded": false,
25+
"sections": [],
26+
}
27+
}
28+
>
29+
<HeaderNavigations
30+
courseActions={
31+
{
32+
"childAddable": true,
33+
"deletable": true,
34+
"draggable": true,
35+
"duplicable": true,
36+
}
37+
}
38+
errors={{}}
39+
hasSections={false}
40+
headerNavigationsActions={
41+
{
42+
"handleExpandAll": [MockFunction],
43+
"handleNewSection": [MockFunction],
44+
"handleReIndex": [MockFunction],
45+
"lmsLink": "",
46+
}
47+
}
48+
isDisabledReindexButton={false}
49+
isReIndexShow={false}
50+
isSectionsExpanded={false}
51+
/>
52+
</PluginSlot>
53+
`;

0 commit comments

Comments
 (0)