Skip to content

Commit d293bb8

Browse files
committed
fix: re-add video sharing options
1 parent a794903 commit d293bb8

12 files changed

Lines changed: 241 additions & 4 deletions

File tree

src/course-outline/CourseOutline.test.tsx

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,14 @@ import {
5252
courseSectionMock,
5353
courseSubsectionMock,
5454
} from './__mocks__';
55-
import { COURSE_BLOCK_NAMES } from './constants';
55+
import { COURSE_BLOCK_NAMES, VIDEO_SHARING_OPTIONS } from './constants';
5656
import CourseOutline from './CourseOutline';
5757

5858
import messages from './messages';
5959
import headerMessages from './header-navigations/messages';
6060
import cardHeaderMessages from './card-header/messages';
6161
import enableHighlightsModalMessages from './enable-highlights-modal/messages';
62+
import statusBarMessages from './status-bar/messages';
6263
import subsectionMessages from './subsection-card/messages';
6364
import pageAlertMessages from './page-alerts/messages';
6465
import {
@@ -242,6 +243,59 @@ describe('<CourseOutline />', () => {
242243
expect(await findByText(messages.alertSuccessDescription.defaultMessage)).toBeInTheDocument();
243244
});
244245

246+
it('check video sharing option udpates correctly', async () => {
247+
const { findByLabelText } = renderComponent();
248+
249+
axiosMock
250+
.onPost(getCourseBlockApiUrl(courseId), {
251+
metadata: {
252+
video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
253+
},
254+
})
255+
.reply(200);
256+
const optionDropdown = await findByLabelText(statusBarMessages.videoSharingTitle.defaultMessage);
257+
await act(
258+
async () => fireEvent.change(optionDropdown, { target: { value: VIDEO_SHARING_OPTIONS.allOff } }),
259+
);
260+
261+
expect(axiosMock.history.post.length).toBe(3);
262+
expect(axiosMock.history.post[2].data).toBe(JSON.stringify({
263+
metadata: {
264+
video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
265+
},
266+
}));
267+
});
268+
269+
it('check video sharing option shows error on failure', async () => {
270+
renderComponent();
271+
272+
axiosMock
273+
.onPost(getCourseBlockApiUrl(courseId), {
274+
metadata: {
275+
video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
276+
},
277+
})
278+
.reply(500);
279+
const optionDropdown = await screen.findByLabelText(statusBarMessages.videoSharingTitle.defaultMessage);
280+
await act(
281+
async () => fireEvent.change(optionDropdown, { target: { value: VIDEO_SHARING_OPTIONS.allOff } }),
282+
);
283+
284+
expect(axiosMock.history.post.length).toBe(3);
285+
expect(axiosMock.history.post[2].data).toBe(JSON.stringify({
286+
metadata: {
287+
video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
288+
},
289+
}));
290+
291+
const alertElements = screen.queryAllByRole('alert');
292+
expect(alertElements.find(
293+
(el) => el.classList.contains('alert-content'),
294+
)).toHaveTextContent(
295+
'Unable to save changes. Please try again.',
296+
);
297+
});
298+
245299
it('render error alert after failed reindex correctly', async () => {
246300
const { findByText, findByTestId } = renderComponent();
247301

src/course-outline/CourseOutline.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ const CourseOutline = () => {
112112
handleDuplicateSectionSubmit,
113113
handleDuplicateSubsectionSubmit,
114114
handleDuplicateUnitSubmit,
115+
handleVideoSharingOptionChange,
115116
handlePasteClipboardClick,
116117
notificationDismissUrl,
117118
discussionsSettings,
@@ -243,6 +244,7 @@ const CourseOutline = () => {
243244
isLoading={isLoading}
244245
statusBarData={statusBarData}
245246
openEnableHighlightsModal={openEnableHighlightsModal}
247+
handleVideoSharingOptionChange={handleVideoSharingOptionChange}
246248
/>
247249
<hr className="mt-4 mb-0 w-100 text-light-400" />
248250
<div className="d-flex align-items-baseline">

src/course-outline/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ export const BEST_PRACTICES_CHECKLIST = {
7373
],
7474
} as const;
7575

76+
export const VIDEO_SHARING_OPTIONS = {
77+
perVideo: 'per-video',
78+
allOn: 'all-on',
79+
allOff: 'all-off',
80+
} as const;
81+
7682
export const API_ERROR_TYPES = {
7783
networkError: 'networkError',
7884
serverError: 'serverError',

src/course-outline/data/api.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,26 @@ export async function setCourseItemOrderList(itemId: string, children: Array<str
454454
return data;
455455
}
456456

457+
/**
458+
* Set video sharing setting
459+
* @param {string} courseId
460+
* @param {string} videoSharingOption
461+
* @returns {Promise<Object>}
462+
*/
463+
export async function setVideoSharingOption(
464+
courseId: string,
465+
videoSharingOption: string,
466+
): Promise<object> {
467+
const { data } = await getAuthenticatedHttpClient()
468+
.post(getCourseBlockApiUrl(courseId), {
469+
metadata: {
470+
video_sharing_options: videoSharingOption,
471+
},
472+
});
473+
474+
return data;
475+
}
476+
457477
/**
458478
* Paste block to clipboard
459479
* @param {string} parentLocator

src/course-outline/data/slice.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { createSlice } from '@reduxjs/toolkit';
33

44
import { RequestStatus } from '@src/data/constants';
5+
import { VIDEO_SHARING_OPTIONS } from '@src/course-outline/constants';
56
import { CourseOutlineState } from './types';
67

78
const initialState = {
@@ -31,6 +32,7 @@ const initialState = {
3132
completedCourseBestPracticesChecks: 0,
3233
},
3334
videoSharingEnabled: false,
35+
videoSharingOptions: VIDEO_SHARING_OPTIONS.perVideo,
3436
},
3537
sectionsList: [],
3638
isCustomRelativeDatesActive: false,

src/course-outline/data/thunk.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getCourseItem,
1616
restartIndexingOnCourse,
1717
setSectionOrderList,
18+
setVideoSharingOption,
1819
setCourseItemOrderList,
1920
dismissNotification,
2021
createDiscussionsTopics,
@@ -157,6 +158,24 @@ export function enableCourseHighlightsEmailsQuery(courseId: string) {
157158
};
158159
}
159160

161+
export function setVideoSharingOptionQuery(courseId: string, option: string) {
162+
return async (dispatch) => {
163+
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
164+
showToastOutsideReact(NOTIFICATION_MESSAGES.saving);
165+
166+
try {
167+
await setVideoSharingOption(courseId, option);
168+
dispatch(updateStatusBar({ videoSharingOptions: option }));
169+
170+
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
171+
} catch {
172+
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
173+
} finally {
174+
closeToastOutsideReact();
175+
}
176+
};
177+
}
178+
160179
export function fetchCourseReindexQuery(reindexLink: string) {
161180
return async (dispatch) => {
162181
dispatch(updateReindexLoadingStatus({ status: RequestStatus.IN_PROGRESS }));

src/course-outline/data/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export interface CourseOutlineStatusBar {
5252
isSelfPaced: boolean;
5353
checklist: ChecklistType;
5454
videoSharingEnabled: boolean;
55+
videoSharingOptions: string;
5556
}
5657

5758
export interface CourseOutlineState {

src/course-outline/hooks.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
fetchCourseBestPracticesQuery,
4040
fetchCourseLaunchQuery,
4141
fetchCourseReindexQuery,
42+
setVideoSharingOptionQuery,
4243
dismissNotificationQuery,
4344
syncDiscussionsTopics,
4445
} from './data/thunk';
@@ -223,6 +224,10 @@ const useCourseOutline = ({ courseId }) => {
223224
handleConfigureModalClose();
224225
};
225226

227+
const handleVideoSharingOptionChange = (value) => {
228+
dispatch(setVideoSharingOptionQuery(courseId, value));
229+
};
230+
226231
const handleDismissNotification = () => {
227232
dispatch(dismissNotificationQuery(`${getConfig().STUDIO_BASE_URL}${notificationDismissUrl}`));
228233
};
@@ -277,6 +282,7 @@ const useCourseOutline = ({ courseId }) => {
277282
handleDuplicateSectionSubmit,
278283
handleDuplicateSubsectionSubmit,
279284
handleDuplicateUnitSubmit,
285+
handleVideoSharingOptionChange,
280286
handlePasteClipboardClick,
281287
notificationDismissUrl,
282288
discussionsSettings,

src/course-outline/status-bar/StatusBar.test.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { VIDEO_SHARING_OPTIONS } from '@src/course-outline/constants';
12
import { CourseOutlineStatusBar } from '@src/course-outline/data/types';
23
import { initializeMocks, render, screen } from '@src/testUtils';
34
import { StatusBar, StatusBarProps } from './StatusBar';
@@ -17,7 +18,8 @@ const statusBarData: CourseOutlineStatusBar = {
1718
completedCourseBestPracticesChecks: 1,
1819
},
1920
highlightsEnabledForMessaging: false,
20-
videoSharingEnabled: true,
21+
videoSharingEnabled: false,
22+
videoSharingOptions: VIDEO_SHARING_OPTIONS.allOn,
2123
};
2224

2325
jest.mock('@src/course-libraries/data/apiHooks', () => ({
@@ -35,6 +37,7 @@ jest.mock('@src/course-outline/data/apiHooks', () => ({
3537
}),
3638
}));
3739
const mockOpenEnableHighlightsModal = jest.fn();
40+
const mockHandleVideoSharingOptionChange = jest.fn();
3841

3942
const renderComponent = (props?: Partial<StatusBarProps>) =>
4043
render(
@@ -43,6 +46,7 @@ const renderComponent = (props?: Partial<StatusBarProps>) =>
4346
isLoading={isLoading}
4447
statusBarData={statusBarData}
4548
openEnableHighlightsModal={mockOpenEnableHighlightsModal}
49+
handleVideoSharingOptionChange={mockHandleVideoSharingOptionChange}
4650
{...props}
4751
/>,
4852
);
@@ -60,6 +64,9 @@ describe('<StatusBar />', () => {
6064
expect(await screen.findByText('Feb 05, 2013 - Apr 09, 2013')).toBeInTheDocument();
6165
expect(await screen.findByText(`2/9 ${messages.checklistCompleted.defaultMessage}`)).toBeInTheDocument();
6266
expect(await screen.findByText('Active')).toBeInTheDocument();
67+
68+
// Video sharing is not enabled by default
69+
expect(screen.queryByText('Video Sharing:')).not.toBeInTheDocument();
6370
});
6471

6572
it('renders Archived Badge', async () => {
@@ -137,4 +144,15 @@ describe('<StatusBar />', () => {
137144
});
138145
expect(await screen.findByText(messages.highlightEmailsEnabled.defaultMessage)).toBeInTheDocument();
139146
});
147+
148+
it('does render video sharing dropdown if enabled', async () => {
149+
renderComponent({
150+
statusBarData: {
151+
...statusBarData,
152+
videoSharingEnabled: true,
153+
},
154+
});
155+
156+
expect(await screen.findByText('Video Sharing:')).toBeInTheDocument();
157+
});
140158
});

src/course-outline/status-bar/StatusBar.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
import {
88
Badge,
99
Button,
10+
Form,
11+
Hyperlink,
1012
Icon,
1113
Stack,
1214
} from '@openedx/paragon';
@@ -20,7 +22,10 @@ import {
2022

2123
import type { ChecklistType, CourseOutlineStatusBar } from '@src/course-outline/data/types';
2224
import { useEntityLinksSummaryByDownstreamContext } from '@src/course-libraries/data/apiHooks';
25+
import { VIDEO_SHARING_OPTIONS } from '@src/course-outline/constants';
2326
import { useCourseDetails } from '@src/course-outline/data/apiHooks';
27+
import { getVideoSharingOptionText } from '@src/course-outline/utils';
28+
import { useHelpUrls } from '@src/help-urls/hooks';
2429

2530
import messages from './messages';
2631
import { NotificationStatusIcon } from './NotificationStatusIcon';
@@ -203,24 +208,73 @@ const Highlights = ({ highlightsEnabledForMessaging, openEnableHighlightsModal }
203208
}
204209
};
205210

211+
const VideoSharingDropdown = ({ handleVideoSharingOptionChange, videoSharingOptions }: {
212+
handleVideoSharingOptionChange: (value: string) => void;
213+
videoSharingOptions: string;
214+
}) => {
215+
const intl = useIntl();
216+
const {
217+
socialSharing: socialSharingUrl,
218+
} = useHelpUrls(['socialSharing']);
219+
220+
return (
221+
<Form.Group
222+
size="sm"
223+
className="d-flex m-0 align-items-center"
224+
>
225+
<Form.Label className="h5 m-0 mr-2 text-gray-700">
226+
<FormattedMessage {...messages.videoSharingTitle} />
227+
</Form.Label>
228+
<div className="d-flex align-items-center">
229+
<Form.Control
230+
as="select"
231+
defaultValue={videoSharingOptions}
232+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleVideoSharingOptionChange(e.target.value)}
233+
>
234+
{Object.values(VIDEO_SHARING_OPTIONS).map((option) => (
235+
<option
236+
key={option}
237+
value={option}
238+
>
239+
{getVideoSharingOptionText(option, messages, intl)}
240+
</option>
241+
))}
242+
</Form.Control>
243+
<Hyperlink
244+
className="small"
245+
destination={socialSharingUrl}
246+
target="_blank"
247+
showLaunchIcon={false}
248+
>
249+
<FormattedMessage {...messages.videoSharingLink} />
250+
</Hyperlink>
251+
</div>
252+
</Form.Group>
253+
);
254+
};
255+
206256
export interface StatusBarProps {
207257
courseId: string;
208258
isLoading: boolean;
209259
statusBarData: CourseOutlineStatusBar;
210260
openEnableHighlightsModal: () => void;
261+
handleVideoSharingOptionChange: (value: string) => void;
211262
}
212263

213264
export const StatusBar = ({
214265
statusBarData,
215266
isLoading,
216267
courseId,
217268
openEnableHighlightsModal,
269+
handleVideoSharingOptionChange,
218270
}: StatusBarProps) => {
219271
const {
220272
endDate,
221273
courseReleaseDate,
222274
highlightsEnabledForMessaging,
223275
checklist,
276+
videoSharingEnabled,
277+
videoSharingOptions,
224278
} = statusBarData;
225279

226280
const courseReleaseDateObj = moment.utc(courseReleaseDate, 'MMM DD, YYYY [at] HH:mm UTC', true);
@@ -246,6 +300,12 @@ export const StatusBar = ({
246300
highlightsEnabledForMessaging={highlightsEnabledForMessaging}
247301
openEnableHighlightsModal={openEnableHighlightsModal}
248302
/>
303+
{videoSharingEnabled && (
304+
<VideoSharingDropdown
305+
handleVideoSharingOptionChange={handleVideoSharingOptionChange}
306+
videoSharingOptions={videoSharingOptions}
307+
/>
308+
)}
249309
<NotificationStatusIcon />
250310
</Stack>
251311
);

0 commit comments

Comments
 (0)