diff --git a/src/course-outline/CourseOutline.jsx b/src/course-outline/CourseOutline.jsx
index d2d2efa91..f361a2b04 100644
--- a/src/course-outline/CourseOutline.jsx
+++ b/src/course-outline/CourseOutline.jsx
@@ -92,6 +92,7 @@ const CourseOutline = ({ courseId }) => {
handleNewUnitSubmit,
getUnitUrl,
handleDragNDrop,
+ handleVideoSharingOptionChange,
} = useCourseOutline({ courseId });
const [sections, setSections] = useState(sectionsList);
@@ -177,6 +178,7 @@ const CourseOutline = ({ courseId }) => {
isLoading={isLoading}
statusBarData={statusBarData}
openEnableHighlightsModal={openEnableHighlightsModal}
+ handleVideoSharingOptionChange={handleVideoSharingOptionChange}
/>
{sections.length ? (
diff --git a/src/course-outline/CourseOutline.test.jsx b/src/course-outline/CourseOutline.test.jsx
index 003ffd0e3..fdf0f70c3 100644
--- a/src/course-outline/CourseOutline.test.jsx
+++ b/src/course-outline/CourseOutline.test.jsx
@@ -14,7 +14,7 @@ import {
getCourseOutlineIndexApiUrl,
getCourseReindexApiUrl,
getXBlockApiUrl,
- getEnableHighlightsEmailsApiUrl,
+ getCourseBlockApiUrl,
getCourseItemApiUrl,
getXBlockBaseApiUrl,
} from './data/api';
@@ -36,12 +36,13 @@ import {
courseSubsectionMock,
} from './__mocks__';
import { executeThunk } from '../utils';
-import { COURSE_BLOCK_NAMES } from './constants';
+import { COURSE_BLOCK_NAMES, VIDEO_SHARING_OPTIONS } from './constants';
import CourseOutline from './CourseOutline';
import messages from './messages';
import headerMessages from './header-navigations/messages';
import cardHeaderMessages from './card-header/messages';
import enableHighlightsModalMessages from './enable-highlights-modal/messages';
+import statusBarMessages from './status-bar/messages';
let axiosMock;
let store;
@@ -114,6 +115,60 @@ describe('', () => {
expect(await findByText(messages.alertSuccessDescription.defaultMessage)).toBeInTheDocument();
});
+ it('check video sharing option udpates correctly', async () => {
+ const { findByTestId } = render();
+
+ axiosMock
+ .onPost(getCourseBlockApiUrl(courseId), {
+ metadata: {
+ video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
+ },
+ })
+ .reply(200);
+ const optionDropdownWrapper = await findByTestId('video-sharing-wrapper');
+ const optionDropdown = await within(optionDropdownWrapper).findByRole('button');
+ await act(async () => fireEvent.click(optionDropdown));
+ const allOffOption = await within(optionDropdownWrapper).findByText(
+ statusBarMessages.videoSharingAllOffText.defaultMessage,
+ );
+ await act(async () => fireEvent.click(allOffOption));
+
+ expect(axiosMock.history.post.length).toBe(1);
+ expect(axiosMock.history.post[0].data).toBe(JSON.stringify({
+ metadata: {
+ video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
+ },
+ }));
+ });
+
+ it('check video sharing option shows error on failure', async () => {
+ const { findByTestId, queryByRole } = render();
+
+ axiosMock
+ .onPost(getCourseBlockApiUrl(courseId), {
+ metadata: {
+ video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
+ },
+ })
+ .reply(500);
+ const optionDropdownWrapper = await findByTestId('video-sharing-wrapper');
+ const optionDropdown = await within(optionDropdownWrapper).findByRole('button');
+ await act(async () => fireEvent.click(optionDropdown));
+ const allOffOption = await within(optionDropdownWrapper).findByText(
+ statusBarMessages.videoSharingAllOffText.defaultMessage,
+ );
+ await act(async () => fireEvent.click(allOffOption));
+
+ expect(axiosMock.history.post.length).toBe(1);
+ expect(axiosMock.history.post[0].data).toBe(JSON.stringify({
+ metadata: {
+ video_sharing_options: VIDEO_SHARING_OPTIONS.allOff,
+ },
+ }));
+
+ expect(queryByRole('alert')).toBeInTheDocument();
+ });
+
it('render error alert after failed reindex correctly', async () => {
const { findByText, findByTestId } = render();
@@ -235,7 +290,7 @@ describe('', () => {
axiosMock.reset();
axiosMock
- .onPost(getEnableHighlightsEmailsApiUrl(courseId), {
+ .onPost(getCourseBlockApiUrl(courseId), {
publish: 'republish',
metadata: {
highlights_enabled_for_messaging: true,
@@ -641,7 +696,7 @@ describe('', () => {
children = children.splice(2, 0, children.splice(0, 1)[0]);
axiosMock
- .onPut(getEnableHighlightsEmailsApiUrl(courseBlockId), { children })
+ .onPut(getCourseBlockApiUrl(courseBlockId), { children })
.reply(200, { dummy: 'value' });
await executeThunk(setSectionOrderListQuery(courseBlockId, children, () => {}), store.dispatch);
@@ -662,7 +717,7 @@ describe('', () => {
const newChildren = children.splice(2, 0, children.splice(0, 1)[0]);
axiosMock
- .onPut(getEnableHighlightsEmailsApiUrl(courseBlockId), { children })
+ .onPut(getCourseBlockApiUrl(courseBlockId), { children })
.reply(500);
await executeThunk(setSectionOrderListQuery(courseBlockId, undefined, () => children), store.dispatch);
diff --git a/src/course-outline/__mocks__/courseOutlineIndex.js b/src/course-outline/__mocks__/courseOutlineIndex.js
index 54d7d2df7..8f206fc68 100644
--- a/src/course-outline/__mocks__/courseOutlineIndex.js
+++ b/src/course-outline/__mocks__/courseOutlineIndex.js
@@ -24,6 +24,8 @@ module.exports = {
'Homework',
'Exam',
],
+ videoSharingEnabled: true,
+ videoSharingOptions: 'per-video',
hasChanges: false,
actions: {
deletable: true,
diff --git a/src/course-outline/constants.js b/src/course-outline/constants.js
index f9a2c8821..bf3ec53ef 100644
--- a/src/course-outline/constants.js
+++ b/src/course-outline/constants.js
@@ -74,3 +74,9 @@ export const BEST_PRACTICES_CHECKLIST = /** @type {const} */ ({
},
],
});
+
+export const VIDEO_SHARING_OPTIONS = /** @type {const} */ ({
+ perVideo: 'per-video',
+ allOn: 'all-on',
+ allOff: 'all-off',
+});
diff --git a/src/course-outline/data/api.js b/src/course-outline/data/api.js
index 702715ce8..df2850fbe 100644
--- a/src/course-outline/data/api.js
+++ b/src/course-outline/data/api.js
@@ -19,7 +19,7 @@ export const getCourseLaunchApiUrl = ({
all,
}) => `${getApiBaseUrl()}/api/courses/v1/validation/${courseId}/?graded_only=${gradedOnly}&validate_oras=${validateOras}&all=${all}`;
-export const getEnableHighlightsEmailsApiUrl = (courseId) => {
+export const getCourseBlockApiUrl = (courseId) => {
const formattedCourseId = courseId.split('course-v1:')[1];
return `${getApiBaseUrl()}/xblock/block-v1:${formattedCourseId}+type@course+block@course`;
};
@@ -112,7 +112,7 @@ export async function getCourseLaunch({
*/
export async function enableCourseHighlightsEmails(courseId) {
const { data } = await getAuthenticatedHttpClient()
- .post(getEnableHighlightsEmailsApiUrl(courseId), {
+ .post(getCourseBlockApiUrl(courseId), {
publish: 'republish',
metadata: {
highlights_enabled_for_messaging: true,
@@ -305,9 +305,26 @@ export async function addNewCourseItem(parentLocator, category, displayName) {
*/
export async function setSectionOrderList(courseId, children) {
const { data } = await getAuthenticatedHttpClient()
- .put(getEnableHighlightsEmailsApiUrl(courseId), {
+ .put(getCourseBlockApiUrl(courseId), {
children,
});
return data;
}
+
+/**
+ * Set video sharing setting
+ * @param {string} courseId
+ * @param {string} videoSharingOption
+ * @returns {Promise
+ {videoSharingEnabled && (
+
+
{intl.formatMessage(messages.videoSharingTitle)}
+
+
+ {Object.values(VIDEO_SHARING_OPTIONS).map((option) => (
+
+ ))}
+
+
+ {intl.formatMessage(messages.videoSharingLink)}
+
+
+
+ )}
);
};
@@ -103,6 +141,7 @@ StatusBar.propTypes = {
courseId: PropTypes.string.isRequired,
isLoading: PropTypes.bool.isRequired,
openEnableHighlightsModal: PropTypes.func.isRequired,
+ handleVideoSharingOptionChange: PropTypes.func.isRequired,
statusBarData: PropTypes.shape({
courseReleaseDate: PropTypes.string.isRequired,
isSelfPaced: PropTypes.bool.isRequired,
@@ -113,6 +152,8 @@ StatusBar.propTypes = {
completedCourseBestPracticesChecks: PropTypes.number.isRequired,
}),
highlightsEnabledForMessaging: PropTypes.bool.isRequired,
+ videoSharingEnabled: PropTypes.bool.isRequired,
+ videoSharingOptions: PropTypes.string.isRequired,
}).isRequired,
};
diff --git a/src/course-outline/status-bar/StatusBar.test.jsx b/src/course-outline/status-bar/StatusBar.test.jsx
index 9745a5c49..9b17be01b 100644
--- a/src/course-outline/status-bar/StatusBar.test.jsx
+++ b/src/course-outline/status-bar/StatusBar.test.jsx
@@ -7,12 +7,14 @@ import { initializeMockApp } from '@edx/frontend-platform';
import StatusBar from './StatusBar';
import messages from './messages';
import initializeStore from '../../store';
+import { VIDEO_SHARING_OPTIONS } from '../constants';
let store;
const mockPathname = '/foo-bar';
const courseId = '123';
const isLoading = false;
const openEnableHighlightsModalMock = jest.fn();
+const handleVideoSharingOptionChange = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
@@ -23,7 +25,8 @@ jest.mock('react-router-dom', () => ({
jest.mock('../../help-urls/hooks', () => ({
useHelpUrls: () => ({
- contentHighlights: 'some',
+ contentHighlights: 'content-highlights-link',
+ socialSharing: 'social-sharing-link',
}),
}));
@@ -38,6 +41,8 @@ const statusBarData = {
},
highlightsEnabledForMessaging: true,
highlightsDocUrl: 'https://example.com/highlights-doc',
+ videoSharingEnabled: true,
+ videoSharingOptions: VIDEO_SHARING_OPTIONS.allOn,
};
const renderComponent = (props) => render(
@@ -47,6 +52,7 @@ const renderComponent = (props) => render(
courseId={courseId}
isLoading={isLoading}
openEnableHighlightsModal={openEnableHighlightsModalMock}
+ handleVideoSharingOptionChange={handleVideoSharingOptionChange}
statusBarData={statusBarData}
{...props}
/>
@@ -68,7 +74,7 @@ describe('', () => {
});
it('renders StatusBar component correctly', () => {
- const { getByText } = renderComponent();
+ const { queryByTestId, getByText } = renderComponent();
expect(getByText(messages.startDateTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(statusBarData.courseReleaseDate)).toBeInTheDocument();
@@ -80,8 +86,9 @@ describe('', () => {
expect(getByText(`2/9 ${messages.checklistCompleted.defaultMessage}`)).toBeInTheDocument();
expect(getByText(messages.highlightEmailsTitle.defaultMessage)).toBeInTheDocument();
- expect(getByText(messages.highlightEmailsLink.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.highlightEmailsEnabled.defaultMessage)).toBeInTheDocument();
+
+ expect(queryByTestId('video-sharing-wrapper')).toBeInTheDocument();
});
it('renders StatusBar when isSelfPaced is false', () => {
@@ -115,4 +122,15 @@ describe('', () => {
expect(queryByTestId('outline-status-bar')).not.toBeInTheDocument();
});
+
+ it('does not render video sharing dropdown if not enabled', () => {
+ const { queryByTestId } = renderComponent({
+ statusBarData: {
+ ...statusBarData,
+ videoSharingEnabled: false,
+ },
+ });
+
+ expect(queryByTestId('video-sharing-wrapper')).not.toBeInTheDocument();
+ });
});
diff --git a/src/course-outline/status-bar/messages.js b/src/course-outline/status-bar/messages.js
index 58ddb2bef..7c8b75ae4 100644
--- a/src/course-outline/status-bar/messages.js
+++ b/src/course-outline/status-bar/messages.js
@@ -41,6 +41,26 @@ const messages = defineMessages({
id: 'course-authoring.course-outline.status-bar.highlight-emails.link',
defaultMessage: 'Learn more',
},
+ videoSharingTitle: {
+ id: 'course-authoring.course-outline.status-bar.video-sharing.title',
+ defaultMessage: 'Video Sharing',
+ },
+ videoSharingLink: {
+ id: 'course-authoring.course-outline.status-bar.video-sharing.title',
+ defaultMessage: 'Learn more',
+ },
+ videoSharingPerVideoText: {
+ id: 'course-authoring.course-outline.status-bar.video-sharing.perVideo.text',
+ defaultMessage: 'Per Video',
+ },
+ videoSharingAllOffText: {
+ id: 'course-authoring.course-outline.status-bar.video-sharing.allOff.text',
+ defaultMessage: 'No Videos',
+ },
+ videoSharingAllOnText: {
+ id: 'course-authoring.course-outline.status-bar.video-sharing.allOn.text',
+ defaultMessage: 'All Videos',
+ },
});
export default messages;
diff --git a/src/course-outline/utils.jsx b/src/course-outline/utils.jsx
index 7853eb310..481dd1f72 100644
--- a/src/course-outline/utils.jsx
+++ b/src/course-outline/utils.jsx
@@ -4,7 +4,7 @@ import {
EditOutline as EditOutlineIcon,
} from '@edx/paragon/icons';
-import { ITEM_BADGE_STATUS, STAFF_ONLY } from './constants';
+import { ITEM_BADGE_STATUS, STAFF_ONLY, VIDEO_SHARING_OPTIONS } from './constants';
/**
* Get section status depended on section info
@@ -128,9 +128,28 @@ const scrollToElement = target => {
}
};
+/**
+ * Get video sharing dropdown translated options.
+ * @param {string} id - option id
+ * @returns {string} - text to display
+ */
+const getVideoSharingOptionText = (id, messages, intl) => {
+ switch (id) {
+ case VIDEO_SHARING_OPTIONS.perVideo:
+ return intl.formatMessage(messages.videoSharingPerVideoText);
+ case VIDEO_SHARING_OPTIONS.allOn:
+ return intl.formatMessage(messages.videoSharingAllOnText);
+ case VIDEO_SHARING_OPTIONS.allOff:
+ return intl.formatMessage(messages.videoSharingAllOffText);
+ default:
+ return '';
+ }
+};
+
export {
getItemStatus,
getItemStatusBadgeContent,
getHighlightsFormValues,
+ getVideoSharingOptionText,
scrollToElement,
};