Skip to content

Commit 294962b

Browse files
committed
feat: [FC-0044] Unit page - Manage access modal (unit & xblocks)
1 parent 60917c6 commit 294962b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2426
-350
lines changed

src/constants.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,10 @@ export const DECODED_ROUTES = {
4949
'/container/:blockId',
5050
],
5151
};
52+
53+
export const COURSE_BLOCK_NAMES = /** @type {const} */ ({
54+
chapter: { id: 'chapter', name: 'Section' },
55+
sequential: { id: 'sequential', name: 'Subsection' },
56+
vertical: { id: 'vertical', name: 'Unit' },
57+
component: { id: 'component', name: 'Component' },
58+
});

src/course-outline/CourseOutline.jsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ import SubHeader from '../generic/sub-header/SubHeader';
2525
import ProcessingNotification from '../generic/processing-notification';
2626
import InternetConnectionAlert from '../generic/internet-connection-alert';
2727
import DeleteModal from '../generic/delete-modal/DeleteModal';
28+
import ConfigureModal from '../generic/configure-modal/ConfigureModal';
2829
import AlertMessage from '../generic/alert-message';
2930
import getPageHeadTitle from '../generic/utils';
30-
import { getCurrentItem } from './data/selectors';
31+
import { getCurrentItem, getProctoredExamsFlag } from './data/selectors';
3132
import { COURSE_BLOCK_NAMES } from './constants';
3233
import HeaderNavigations from './header-navigations/HeaderNavigations';
3334
import OutlineSideBar from './outline-sidebar/OutlineSidebar';
@@ -39,7 +40,6 @@ import UnitCard from './unit-card/UnitCard';
3940
import HighlightsModal from './highlights-modal/HighlightsModal';
4041
import EmptyPlaceholder from './empty-placeholder/EmptyPlaceholder';
4142
import PublishModal from './publish-modal/PublishModal';
42-
import ConfigureModal from './configure-modal/ConfigureModal';
4343
import PageAlerts from './page-alerts/PageAlerts';
4444
import { useCourseOutline } from './hooks';
4545
import messages from './messages';
@@ -118,8 +118,10 @@ const CourseOutline = ({ courseId }) => {
118118
title: processingNotificationTitle,
119119
} = useSelector(getProcessingNotification);
120120

121-
const { category } = useSelector(getCurrentItem);
122-
const deleteCategory = COURSE_BLOCK_NAMES[category]?.name.toLowerCase();
121+
const currentItemData = useSelector(getCurrentItem);
122+
const deleteCategory = COURSE_BLOCK_NAMES[currentItemData.category]?.name.toLowerCase();
123+
124+
const enableProctoredExams = useSelector(getProctoredExamsFlag);
123125

124126
const finalizeSectionOrder = () => (newSections) => {
125127
initialSections = [...sectionsList];
@@ -485,6 +487,8 @@ const CourseOutline = ({ courseId }) => {
485487
isOpen={isConfigureModalOpen}
486488
onClose={handleConfigureModalClose}
487489
onConfigureSubmit={handleConfigureItemSubmit}
490+
currentItemData={currentItemData}
491+
enableProctoredExams={enableProctoredExams}
488492
/>
489493
<DeleteModal
490494
category={deleteCategory}

src/course-outline/CourseOutline.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
@import "./empty-placeholder/EmptyPlaceholder";
88
@import "./highlights-modal/HighlightsModal";
99
@import "./publish-modal/PublishModal";
10-
@import "./configure-modal/ConfigureModal";
1110
@import "./drag-helper/ConditionalSortableElement";
1211
@import "./xblock-status/XBlockStatus";
1312
@import "./paste-button/PasteButton";

src/course-outline/CourseOutline.test.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,16 @@ import {
3838
import { executeThunk } from '../utils';
3939
import { COURSE_BLOCK_NAMES, VIDEO_SHARING_OPTIONS } from './constants';
4040
import CourseOutline from './CourseOutline';
41-
import messages from './messages';
41+
42+
import configureModalMessages from '../generic/configure-modal/messages';
4243
import headerMessages from './header-navigations/messages';
4344
import cardHeaderMessages from './card-header/messages';
4445
import enableHighlightsModalMessages from './enable-highlights-modal/messages';
4546
import statusBarMessages from './status-bar/messages';
46-
import configureModalMessages from './configure-modal/messages';
4747
import pasteButtonMessages from './paste-button/messages';
4848
import subsectionMessages from './subsection-card/messages';
4949
import pageAlertMessages from './page-alerts/messages';
50+
import messages from './messages';
5051

5152
let axiosMock;
5253
let store;

src/course-unit/CourseUnit.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const CourseUnit = ({ courseId }) => {
4848
handleTitleEdit,
4949
handleInternetConnectionFailed,
5050
handleCreateNewCourseXBlock,
51+
handleConfigureSubmit,
5152
courseVerticalChildren,
5253
} = useCourseUnit({ courseId, blockId });
5354

@@ -85,6 +86,7 @@ const CourseUnit = ({ courseId }) => {
8586
isTitleEditFormOpen={isTitleEditFormOpen}
8687
handleTitleEdit={handleTitleEdit}
8788
handleTitleEditSubmit={handleTitleEditSubmit}
89+
handleConfigureSubmit={handleConfigureSubmit}
8890
/>
8991
)}
9092
breadcrumbs={(
@@ -118,14 +120,20 @@ const CourseUnit = ({ courseId }) => {
118120
/>
119121
)}
120122
<Stack gap={4} className="mb-4">
121-
{courseVerticalChildren.children.map(({ name, blockId: id, shouldScroll }) => (
123+
{courseVerticalChildren.children.map(({
124+
name, blockId: id, shouldScroll, userPartitionInfo, validationMessages,
125+
}) => (
122126
<CourseXBlock
123127
id={id}
124128
key={id}
125129
title={name}
130+
blockId={blockId}
131+
validationMessages={validationMessages}
126132
shouldScroll={shouldScroll}
127133
unitXBlockActions={unitXBlockActions}
134+
handleConfigureSubmit={handleConfigureSubmit}
128135
data-testid="course-xblock"
136+
userPartitionInfo={userPartitionInfo}
129137
/>
130138
))}
131139
</Stack>

src/course-unit/CourseUnit.test.jsx

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,14 @@ import courseSequenceMessages from './course-sequence/messages';
3838
import sidebarMessages from './sidebar/messages';
3939
import { extractCourseUnitId } from './sidebar/utils';
4040
import CourseUnit from './CourseUnit';
41-
import messages from './messages';
4241

4342
import deleteModalMessages from '../generic/delete-modal/messages';
43+
import configureModalMessages from '../generic/configure-modal/messages';
4444
import courseXBlockMessages from './course-xblock/messages';
4545
import addComponentMessages from './add-component/messages';
4646
import { PUBLISH_TYPES, UNIT_VISIBILITY_STATES } from './constants';
4747
import { getContentTaxonomyTagsApiUrl, getContentTaxonomyTagsCountApiUrl } from '../content-tags-drawer/data/api';
48+
import messages from './messages';
4849

4950
let axiosMock;
5051
let store;
@@ -571,6 +572,7 @@ describe('<CourseUnit />', () => {
571572
name: 'New Cloned XBlock',
572573
block_id: '1234567890',
573574
block_type: 'drag-and-drop-v2',
575+
user_partition_info: {},
574576
},
575577
],
576578
});
@@ -594,7 +596,7 @@ describe('<CourseUnit />', () => {
594596
});
595597
});
596598

597-
it('should toggle visibility and update course unit state accordingly', async () => {
599+
it('should toggle visibility from sidebar and update course unit state accordingly', async () => {
598600
const { getByRole, getByTestId } = render(<RootWrapper />);
599601
let courseUnitSidebar;
600602
let draftUnpublishedChangesHeading;
@@ -617,7 +619,7 @@ describe('<CourseUnit />', () => {
617619
axiosMock
618620
.onPost(getXBlockBaseApiUrl(blockId), {
619621
publish: PUBLISH_TYPES.republish,
620-
metadata: { visible_to_staff_only: true },
622+
metadata: { visible_to_staff_only: true, group_access: null },
621623
})
622624
.reply(200, { dummy: 'value' });
623625
axiosMock
@@ -654,7 +656,7 @@ describe('<CourseUnit />', () => {
654656
axiosMock
655657
.onPost(getXBlockBaseApiUrl(blockId), {
656658
publish: PUBLISH_TYPES.republish,
657-
metadata: { visible_to_staff_only: null },
659+
metadata: { visible_to_staff_only: null, group_access: null },
658660
})
659661
.reply(200, { dummy: 'value' });
660662
axiosMock
@@ -942,4 +944,73 @@ describe('<CourseUnit />', () => {
942944
.replace('{sectionName}', courseUnitIndexMock.release_date_from),
943945
)).toBeInTheDocument();
944946
});
947+
948+
it('should toggle visibility from header configure modal and update course unit state accordingly', async () => {
949+
const { getByRole, getByTestId } = render(<RootWrapper />);
950+
let courseUnitSidebar;
951+
let sidebarVisibilityCheckbox;
952+
let modalVisibilityCheckbox;
953+
let configureModal;
954+
let restrictAccessSelect;
955+
956+
await waitFor(() => {
957+
courseUnitSidebar = getByTestId('course-unit-sidebar');
958+
sidebarVisibilityCheckbox = within(courseUnitSidebar)
959+
.getByLabelText(sidebarMessages.visibilityCheckboxTitle.defaultMessage);
960+
expect(sidebarVisibilityCheckbox).not.toBeChecked();
961+
962+
const headerConfigureBtn = getByRole('button', { name: /settings/i });
963+
expect(headerConfigureBtn).toBeInTheDocument();
964+
965+
userEvent.click(headerConfigureBtn);
966+
configureModal = getByTestId('configure-modal');
967+
restrictAccessSelect = within(configureModal)
968+
.getByRole('combobox', { name: configureModalMessages.restrictAccessTo.defaultMessage });
969+
expect(within(configureModal)
970+
.getByText(configureModalMessages.unitVisibility.defaultMessage)).toBeInTheDocument();
971+
expect(within(configureModal)
972+
.getByText(configureModalMessages.restrictAccessTo.defaultMessage)).toBeInTheDocument();
973+
expect(restrictAccessSelect).toBeInTheDocument();
974+
expect(restrictAccessSelect).toHaveValue('-1');
975+
976+
modalVisibilityCheckbox = within(configureModal)
977+
.getByRole('checkbox', { name: configureModalMessages.hideFromLearners.defaultMessage });
978+
expect(modalVisibilityCheckbox).not.toBeChecked();
979+
980+
userEvent.click(modalVisibilityCheckbox);
981+
expect(modalVisibilityCheckbox).toBeChecked();
982+
983+
userEvent.selectOptions(restrictAccessSelect, '0');
984+
const [, group1Checkbox] = within(configureModal).getAllByRole('checkbox');
985+
986+
userEvent.click(group1Checkbox);
987+
expect(group1Checkbox).toBeChecked();
988+
});
989+
990+
axiosMock
991+
.onPost(getXBlockBaseApiUrl(courseUnitIndexMock.id), {
992+
publish: null,
993+
metadata: { visible_to_staff_only: true, group_access: { 50: [2] } },
994+
})
995+
.reply(200, { dummy: 'value' });
996+
axiosMock
997+
.onGet(getCourseUnitApiUrl(blockId))
998+
.replyOnce(200, {
999+
...courseUnitIndexMock,
1000+
visibility_state: UNIT_VISIBILITY_STATES.staffOnly,
1001+
has_explicit_staff_lock: true,
1002+
});
1003+
1004+
const modalSaveBtn = within(configureModal)
1005+
.getByRole('button', { name: configureModalMessages.saveButton.defaultMessage });
1006+
userEvent.click(modalSaveBtn);
1007+
1008+
await waitFor(() => {
1009+
expect(sidebarVisibilityCheckbox).toBeChecked();
1010+
expect(within(courseUnitSidebar)
1011+
.getByText(sidebarMessages.sidebarTitleVisibleToStaffOnly.defaultMessage)).toBeInTheDocument();
1012+
expect(within(courseUnitSidebar)
1013+
.getByText(sidebarMessages.visibilityStaffOnlyTitle.defaultMessage)).toBeInTheDocument();
1014+
});
1015+
});
9451016
});

src/course-unit/__mocks__/courseVerticalChildren.js

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,146 @@ module.exports = {
22
children: [
33
{
44
name: 'Discussion',
5-
block_id: 'block-v1:OpenedX+L153+3T2023+type@discussion+block@fecd20842dd24f50bdc06643e791b013',
5+
block_id: 'block-v1:OpenedX+L153+3T2023+type@discussion+block@5a28279f24344723a96b1268d3b7cfc0',
66
block_type: 'discussion',
7+
actions: {
8+
can_copy: true,
9+
can_duplicate: true,
10+
can_move: true,
11+
can_manage_access: true,
12+
can_delete: true,
13+
},
14+
user_partition_info: {
15+
selectable_partitions: [
16+
{
17+
id: 970807507,
18+
name: 'Content Groups',
19+
scheme: 'cohort',
20+
groups: [
21+
{
22+
id: 1959537066,
23+
name: 'Group 1',
24+
selected: false,
25+
deleted: false,
26+
},
27+
{
28+
id: 108068059,
29+
name: 'Group 2',
30+
selected: false,
31+
deleted: false,
32+
},
33+
],
34+
},
35+
],
36+
selected_partition_index: -1,
37+
selected_groups_label: '',
38+
},
39+
user_partitions: [
40+
{
41+
id: 970807507,
42+
name: 'Content Groups',
43+
scheme: 'cohort',
44+
groups: [
45+
{
46+
id: 1959537066,
47+
name: 'Group 1',
48+
selected: false,
49+
deleted: false,
50+
},
51+
{
52+
id: 108068059,
53+
name: 'Group 2',
54+
selected: false,
55+
deleted: false,
56+
},
57+
],
58+
},
59+
{
60+
id: 50,
61+
name: 'Enrollment Track Groups',
62+
scheme: 'enrollment_track',
63+
groups: [
64+
{
65+
id: 1,
66+
name: 'Audit',
67+
selected: false,
68+
deleted: false,
69+
},
70+
],
71+
},
72+
],
773
},
874
{
975
name: 'Drag and Drop',
1076
block_id: 'block-v1:OpenedX+L153+3T2023+type@drag-and-drop-v2+block@b33cf1f6df4c41639659bc91132eeb02',
1177
block_type: 'drag-and-drop-v2',
78+
actions: {
79+
can_copy: true,
80+
can_duplicate: true,
81+
can_move: true,
82+
can_manage_access: true,
83+
can_delete: true,
84+
},
85+
user_partition_info: {
86+
selectable_partitions: [
87+
{
88+
id: 970807507,
89+
name: 'Content Groups',
90+
scheme: 'cohort',
91+
groups: [
92+
{
93+
id: 1959537066,
94+
name: 'Group 1',
95+
selected: false,
96+
deleted: false,
97+
},
98+
{
99+
id: 108068059,
100+
name: 'Group 2',
101+
selected: false,
102+
deleted: false,
103+
},
104+
],
105+
},
106+
],
107+
selected_partition_index: -1,
108+
selected_groups_label: '',
109+
},
110+
user_partitions: [
111+
{
112+
id: 970807507,
113+
name: 'Content Groups',
114+
scheme: 'cohort',
115+
groups: [
116+
{
117+
id: 1959537066,
118+
name: 'Group 1',
119+
selected: false,
120+
deleted: false,
121+
},
122+
{
123+
id: 108068059,
124+
name: 'Group 2',
125+
selected: false,
126+
deleted: false,
127+
},
128+
],
129+
},
130+
{
131+
id: 50,
132+
name: 'Enrollment Track Groups',
133+
scheme: 'enrollment_track',
134+
groups: [
135+
{
136+
id: 1,
137+
name: 'Audit',
138+
selected: false,
139+
deleted: false,
140+
},
141+
],
142+
},
143+
],
12144
},
13145
],
14-
is_published: false,
146+
isPublished: false,
15147
};

0 commit comments

Comments
 (0)