diff --git a/src/course-outline/CourseOutline.jsx b/src/course-outline/CourseOutline.jsx
index 51bf39f26..6c9df68a8 100644
--- a/src/course-outline/CourseOutline.jsx
+++ b/src/course-outline/CourseOutline.jsx
@@ -10,24 +10,35 @@ import {
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
} from '@edx/paragon/icons';
+import { useSelector } from 'react-redux';
-import SubHeader from '../generic/sub-header/SubHeader';
+import { getProcessingNotification } from '../generic/processing-notification/data/selectors';
import { RequestStatus } from '../data/constants';
+import SubHeader from '../generic/sub-header/SubHeader';
+import ProcessingNotification from '../generic/processing-notification';
import InternetConnectionAlert from '../generic/internet-connection-alert';
import AlertMessage from '../generic/alert-message';
+import getPageHeadTitle from '../generic/utils';
import HeaderNavigations from './header-navigations/HeaderNavigations';
import OutlineSideBar from './outline-sidebar/OutlineSidebar';
-import messages from './messages';
-import { useCourseOutline } from './hooks';
import StatusBar from './status-bar/StatusBar';
import EnableHighlightsModal from './enable-highlights-modal/EnableHighlightsModal';
+import SectionCard from './section-card/SectionCard';
+import HighlightsModal from './highlights-modal/HighlightsModal';
+import EmptyPlaceholder from './empty-placeholder/EmptyPlaceholder';
+import PublishModal from './publish-modal/PublishModal';
+import DeleteModal from './delete-modal/DeleteModal';
+import { useCourseOutline } from './hooks';
+import messages from './messages';
const CourseOutline = ({ courseId }) => {
const intl = useIntl();
const {
+ courseName,
savingStatus,
statusBarData,
+ sectionsList,
isLoading,
isReIndexShow,
showErrorAlert,
@@ -36,13 +47,34 @@ const CourseOutline = ({ courseId }) => {
isEnableHighlightsModalOpen,
isInternetConnectionAlertFailed,
isDisabledReindexButton,
+ isHighlightsModalOpen,
+ isPublishModalOpen,
+ isDeleteModalOpen,
+ closeHighlightsModal,
+ closePublishModal,
+ closeDeleteModal,
+ openPublishModal,
+ openDeleteModal,
headerNavigationsActions,
openEnableHighlightsModal,
closeEnableHighlightsModal,
handleEnableHighlightsSubmit,
handleInternetConnectionFailed,
+ handleOpenHighlightsModal,
+ handleHighlightsFormSubmit,
+ handlePublishSectionSubmit,
+ handleEditSectionSubmit,
+ handleDeleteSectionSubmit,
+ handleDuplicateSectionSubmit,
} = useCourseOutline({ courseId });
+ document.title = getPageHeadTitle(courseName, intl.formatMessage(messages.headingTitle));
+
+ const {
+ isShow: isShowProcessingNotification,
+ title: processingNotificationTitle,
+ } = useSelector(getProcessingNotification);
+
if (isLoading) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>>;
@@ -77,6 +109,7 @@ const CourseOutline = ({ courseId }) => {
isSectionsExpanded={isSectionsExpanded}
headerNavigationsActions={headerNavigationsActions}
isDisabledReindexButton={isDisabledReindexButton}
+ hasSections={Boolean(sectionsList.length)}
/>
)}
/>
@@ -97,6 +130,23 @@ const CourseOutline = ({ courseId }) => {
statusBarData={statusBarData}
openEnableHighlightsModal={openEnableHighlightsModal}
/>
+
+ {/* TODO add create new section handler in EmptyPlaceholder */}
+ {sectionsList.length ? sectionsList.map((section) => (
+
+ )) : (
+ ({})} />
+ )}
+
@@ -109,11 +159,29 @@ const CourseOutline = ({ courseId }) => {
isOpen={isEnableHighlightsModalOpen}
close={closeEnableHighlightsModal}
onEnableHighlightsSubmit={handleEnableHighlightsSubmit}
- highlightsDocUrl={statusBarData.highlightsDocUrl}
/>
+
+
', () => {
await executeThunk(enableCourseHighlightsEmailsQuery(courseId), store.dispatch);
expect(await findByTestId('highlights-enabled-span')).toBeInTheDocument();
});
+
+ it('should expand and collapse subsections, after click on subheader buttons', async () => {
+ const { queryAllByTestId, getByText } = render(
);
+
+ await waitFor(() => {
+ const collapseBtn = getByText(messages.collapseAllButton.defaultMessage);
+ expect(collapseBtn).toBeInTheDocument();
+ fireEvent.click(collapseBtn);
+
+ const expendBtn = getByText(messages.expandAllButton.defaultMessage);
+ expect(expendBtn).toBeInTheDocument();
+
+ fireEvent.click(expendBtn);
+
+ const cardSubsections = queryAllByTestId('section-card__subsections');
+ cardSubsections.forEach(element => expect(element).toBeVisible());
+
+ fireEvent.click(collapseBtn);
+ cardSubsections.forEach(element => expect(element).not.toBeVisible());
+ });
+ });
+
+ it('render CourseOutline component without sections correctly', async () => {
+ cleanup();
+ axiosMock
+ .onGet(getCourseOutlineIndexApiUrl(courseId))
+ .reply(200, courseOutlineIndexWithoutSections);
+
+ const { getByTestId } = render(
);
+
+ await waitFor(() => {
+ expect(getByTestId('empty-placeholder')).toBeInTheDocument();
+ });
+ });
+
+ it('check edit section when edit query is successfully', async () => {
+ const { getByText } = render(
);
+ const newDisplayName = 'New section name';
+
+ const section = courseOutlineIndexMock.courseStructure.childInfo.children[0];
+
+ axiosMock
+ .onPost(getUpdateCourseSectionApiUrl(section.id, {
+ metadata: {
+ display_name: newDisplayName,
+ },
+ }))
+ .reply(200);
+
+ await executeThunk(editCourseSectionQuery(section.id, newDisplayName), store.dispatch);
+
+ axiosMock
+ .onGet(getCourseSectionApiUrl(section.id))
+ .reply(200);
+ await executeThunk(fetchCourseSectionQuery(section.id), store.dispatch);
+
+ await waitFor(() => {
+ expect(getByText(section.displayName)).toBeInTheDocument();
+ });
+ });
+
+ it('check delete section when edit query is successfully', async () => {
+ const { queryByText } = render(
);
+ const section = courseOutlineIndexMock.courseStructure.childInfo.children[1];
+
+ axiosMock.onDelete(getUpdateCourseSectionApiUrl(section.id)).reply(200);
+ await executeThunk(deleteCourseSectionQuery(section.id), store.dispatch);
+
+ await waitFor(() => {
+ expect(queryByText(section.displayName)).not.toBeInTheDocument();
+ });
+ });
+
+ it('check duplicate section when duplicate query is successfully', async () => {
+ const { getAllByTestId } = render(
);
+ const section = courseOutlineIndexMock.courseStructure.childInfo.children[0];
+ const courseBlockId = courseOutlineIndexMock.courseStructure.id;
+
+ axiosMock
+ .onPost(getCourseSectionDuplicateApiUrl())
+ .reply(200, {
+ duplicate_source_locator: section.id,
+ parent_locator: courseBlockId,
+ });
+ await executeThunk(duplicateCourseSectionQuery(section.id, courseBlockId), store.dispatch);
+
+ await waitFor(() => {
+ expect(getAllByTestId('section-card')).toHaveLength(4);
+ });
+ });
+
+ it('check publish section when publish query is successfully', async () => {
+ cleanup();
+ const { getAllByTestId } = render(
);
+ const section = courseOutlineIndexMock.courseStructure.childInfo.children[0];
+
+ axiosMock
+ .onGet(getCourseOutlineIndexApiUrl(courseId))
+ .reply(200, {
+ courseOutlineIndexMock,
+ courseStructure: {
+ childInfo: {
+ children: [
+ {
+ ...section,
+ published: false,
+ },
+ ],
+ },
+ },
+ });
+
+ axiosMock
+ .onPost(getUpdateCourseSectionApiUrl(section.id), {
+ publish: 'make_public',
+ })
+ .reply(200);
+
+ await executeThunk(publishCourseSectionQuery(section.id), store.dispatch);
+
+ axiosMock
+ .onGet(getCourseSectionApiUrl(section.id))
+ .reply(200, {
+ ...section,
+ published: true,
+ releasedToStudents: false,
+ });
+
+ await executeThunk(fetchCourseSectionQuery(section.id), store.dispatch);
+
+ const firstSection = getAllByTestId('section-card')[0];
+ expect(firstSection.querySelector('.section-card-header__badge-status')).toHaveTextContent('Published not live');
+ });
+
+ it('check update highlights when update highlights query is successfully', async () => {
+ const { getByRole } = render(
);
+
+ const section = courseOutlineIndexMock.courseStructure.childInfo.children[0];
+ const highlights = [
+ 'New Highlight 1',
+ 'New Highlight 2',
+ 'New Highlight 3',
+ 'New Highlight 4',
+ 'New Highlight 5',
+ ];
+
+ axiosMock
+ .onPost(getUpdateCourseSectionApiUrl(section.id), {
+ publish: 'republish',
+ metadata: {
+ highlights,
+ },
+ })
+ .reply(200);
+
+ await executeThunk(updateCourseSectionHighlightsQuery(section.id, highlights), store.dispatch);
+
+ axiosMock
+ .onGet(getCourseSectionApiUrl(section.id))
+ .reply(200, {
+ ...section,
+ highlights,
+ });
+
+ await executeThunk(fetchCourseSectionQuery(section.id), store.dispatch);
+
+ expect(getByRole('button', { name: '5 Section highlights' })).toBeInTheDocument();
+ });
});
diff --git a/src/course-outline/card-header/CardHeader.jsx b/src/course-outline/card-header/CardHeader.jsx
new file mode 100644
index 000000000..b8a61a44e
--- /dev/null
+++ b/src/course-outline/card-header/CardHeader.jsx
@@ -0,0 +1,165 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { useIntl } from '@edx/frontend-platform/i18n';
+import {
+ Button,
+ Dropdown,
+ Form,
+ Icon,
+ IconButton,
+ OverlayTrigger,
+ Tooltip,
+ Truncate,
+} from '@edx/paragon';
+import {
+ ArrowDropDown as ArrowDownIcon,
+ MoreVert as MoveVertIcon,
+ EditOutline as EditIcon,
+} from '@edx/paragon/icons';
+import classNames from 'classnames';
+
+import { useEscapeClick } from '../../hooks';
+import { SECTION_BADGE_STATUTES } from '../constants';
+import { getSectionStatusBadgeContent } from '../utils';
+import messages from './messages';
+
+const CardHeader = ({
+ title,
+ sectionStatus,
+ isExpanded,
+ onClickPublish,
+ onClickMenuButton,
+ onClickEdit,
+ onExpand,
+ isFormOpen,
+ onEditSubmit,
+ closeForm,
+ isDisabledEditField,
+ onClickDelete,
+ onClickDuplicate,
+}) => {
+ const intl = useIntl();
+ const [titleValue, setTitleValue] = useState(title);
+
+ const { badgeTitle, badgeIcon } = getSectionStatusBadgeContent(sectionStatus, messages, intl);
+ const isDisabledPublish = sectionStatus === SECTION_BADGE_STATUTES.live
+ || sectionStatus === SECTION_BADGE_STATUTES.publishedNotLive;
+
+ useEscapeClick({
+ onEscape: () => {
+ setTitleValue(title);
+ closeForm();
+ },
+ dependency: title,
+ });
+
+ return (
+
+ {isFormOpen ? (
+
+ e && e.focus()}
+ value={titleValue}
+ name="displayName"
+ onChange={(e) => setTitleValue(e.target.value)}
+ aria-label="edit field"
+ onBlur={() => onEditSubmit(titleValue)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ onEditSubmit(titleValue);
+ }
+ }}
+ disabled={isDisabledEditField}
+ />
+
+ ) : (
+
+ {intl.formatMessage(messages.expandTooltip)}
+
+ )}
+ >
+
+
+ )}
+
+ {!isFormOpen && (
+
+ )}
+
+
+
+
+ {intl.formatMessage(messages.menuPublish)}
+
+ {intl.formatMessage(messages.menuConfigure)}
+ {intl.formatMessage(messages.menuDuplicate)}
+ {intl.formatMessage(messages.menuDelete)}
+
+
+
+
+ );
+};
+
+CardHeader.propTypes = {
+ title: PropTypes.string.isRequired,
+ sectionStatus: PropTypes.string.isRequired,
+ isExpanded: PropTypes.bool.isRequired,
+ onExpand: PropTypes.func.isRequired,
+ onClickPublish: PropTypes.func.isRequired,
+ onClickMenuButton: PropTypes.func.isRequired,
+ onClickEdit: PropTypes.func.isRequired,
+ isFormOpen: PropTypes.bool.isRequired,
+ onEditSubmit: PropTypes.func.isRequired,
+ closeForm: PropTypes.func.isRequired,
+ isDisabledEditField: PropTypes.bool.isRequired,
+ onClickDelete: PropTypes.func.isRequired,
+ onClickDuplicate: PropTypes.func.isRequired,
+};
+
+export default CardHeader;
diff --git a/src/course-outline/card-header/CardHeader.scss b/src/course-outline/card-header/CardHeader.scss
new file mode 100644
index 000000000..f12261e9c
--- /dev/null
+++ b/src/course-outline/card-header/CardHeader.scss
@@ -0,0 +1,65 @@
+.section-card-header {
+ display: flex;
+ align-items: center;
+ margin-right: -.5rem;
+
+ .section-card-header__expanded-btn {
+ justify-content: flex-start;
+ padding: 0;
+ width: 80%;
+ height: 1.5rem;
+ margin-right: .25rem;
+ background: transparent;
+
+ &::before {
+ display: none;
+ }
+
+ & svg {
+ width: 1.5rem;
+ height: 1.5rem;
+ }
+
+ &.collapsed > .pgn__icon {
+ transform: rotate(180deg);
+ }
+
+ & span:first-child {
+ color: $black;
+ }
+ }
+
+ .section-card-header__badge-status {
+ display: flex;
+ padding: 1px .5rem;
+ justify-content: center;
+ align-items: center;
+ gap: .25rem;
+ border-radius: .375rem;
+ border: 1px solid $light-300;
+ margin: 0 .75rem;
+
+ & span:last-child {
+ color: $primary-700;
+ }
+ }
+
+ .section-card-header__menu {
+ display: flex;
+ align-items: center;
+ }
+
+ .pgn__form-group {
+ width: 80%;
+ }
+}
+
+.section-card-header-tooltip {
+ .tooltip-inner {
+ max-width: 18.75rem;
+ }
+
+ .arrow {
+ transform: translate(5.75rem, 0) !important;
+ }
+}
diff --git a/src/course-outline/card-header/CardHeader.test.jsx b/src/course-outline/card-header/CardHeader.test.jsx
new file mode 100644
index 000000000..139a21035
--- /dev/null
+++ b/src/course-outline/card-header/CardHeader.test.jsx
@@ -0,0 +1,176 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
+
+import { SECTION_BADGE_STATUTES } from '../constants';
+import CardHeader from './CardHeader';
+import messages from './messages';
+
+const onExpandMock = jest.fn();
+const onClickMenuButtonMock = jest.fn();
+const onClickPublishMock = jest.fn();
+const onClickEditMock = jest.fn();
+const onClickDeleteMock = jest.fn();
+const onClickDuplicateMock = jest.fn();
+const closeFormMock = jest.fn();
+
+const cardHeaderProps = {
+ title: 'Some title',
+ sectionStatus: SECTION_BADGE_STATUTES.live,
+ isExpanded: true,
+ onExpand: onExpandMock,
+ onClickMenuButton: onClickMenuButtonMock,
+ onClickPublish: onClickPublishMock,
+ onClickEdit: onClickEditMock,
+ isFormOpen: false,
+ onEditSubmit: jest.fn(),
+ closeForm: closeFormMock,
+ isDisabledEditField: false,
+ onClickDelete: onClickDeleteMock,
+ onClickDuplicate: onClickDuplicateMock,
+};
+
+const renderComponent = (props) => render(
+
+
+ ,
+);
+
+describe('
', () => {
+ it('render CardHeader component correctly', () => {
+ const { getByText, getByTestId, queryByTestId } = renderComponent();
+
+ expect(getByText(cardHeaderProps.title)).toBeInTheDocument();
+ expect(getByTestId('section-card-header__expanded-btn')).toBeInTheDocument();
+ expect(getByTestId('section-card-header__badge-status')).toBeInTheDocument();
+ expect(getByTestId('section-card-header__menu')).toBeInTheDocument();
+ expect(queryByTestId('edit field')).not.toBeInTheDocument();
+ });
+
+ it('render status badge as live', () => {
+ const { getByText } = renderComponent();
+ expect(getByText(messages.statusBadgeLive.defaultMessage)).toBeInTheDocument();
+ });
+
+ it('render status badge as published_not_live', () => {
+ const { getByText } = renderComponent({
+ ...cardHeaderProps,
+ sectionStatus: SECTION_BADGE_STATUTES.publishedNotLive,
+ });
+
+ expect(getByText(messages.statusBadgePublishedNotLive.defaultMessage)).toBeInTheDocument();
+ });
+
+ it('render status badge as staff_only', () => {
+ const { getByText } = renderComponent({
+ ...cardHeaderProps,
+ sectionStatus: SECTION_BADGE_STATUTES.staffOnly,
+ });
+
+ expect(getByText(messages.statusBadgeStuffOnly.defaultMessage)).toBeInTheDocument();
+ });
+
+ it('render status badge as draft', () => {
+ const { getByText } = renderComponent({
+ ...cardHeaderProps,
+ sectionStatus: SECTION_BADGE_STATUTES.draft,
+ });
+
+ expect(getByText(messages.statusBadgeDraft.defaultMessage)).toBeInTheDocument();
+ });
+
+ it('check publish menu item is disabled when section status is live or published not live', async () => {
+ const { getByText, getByTestId } = renderComponent({
+ ...cardHeaderProps,
+ sectionStatus: SECTION_BADGE_STATUTES.publishedNotLive,
+ });
+
+ const menuButton = getByTestId('section-card-header__menu-button');
+ fireEvent.click(menuButton);
+ expect(getByText(messages.menuPublish.defaultMessage)).toHaveAttribute('aria-disabled', 'true');
+ });
+
+ it('calls handleExpanded when button is clicked', () => {
+ const { getByTestId } = renderComponent();
+
+ const expandButton = getByTestId('section-card-header__expanded-btn');
+ fireEvent.click(expandButton);
+ expect(onExpandMock).toHaveBeenCalled();
+ });
+
+ it('calls onClickMenuButton when menu is clicked', () => {
+ const { getByTestId } = renderComponent();
+
+ const menuButton = getByTestId('section-card-header__menu-button');
+ fireEvent.click(menuButton);
+ expect(onClickMenuButtonMock).toHaveBeenCalled();
+ });
+
+ it('calls onClickPublish when item is clicked', () => {
+ const { getByText, getByTestId } = renderComponent({
+ ...cardHeaderProps,
+ sectionStatus: SECTION_BADGE_STATUTES.draft,
+ });
+
+ const menuButton = getByTestId('section-card-header__menu-button');
+ fireEvent.click(menuButton);
+
+ const publishMenuItem = getByText(messages.menuPublish.defaultMessage);
+ fireEvent.click(publishMenuItem);
+ expect(onClickPublishMock).toHaveBeenCalled();
+ });
+
+ it('calls onClickEdit when the button is clicked', () => {
+ const { getByTestId } = renderComponent();
+
+ const editButton = getByTestId('edit-button');
+ fireEvent.click(editButton);
+ expect(onClickEditMock).toHaveBeenCalled();
+ });
+
+ it('check is field visible when isFormOpen is true', () => {
+ const { getByTestId, queryByTestId } = renderComponent({
+ ...cardHeaderProps,
+ isFormOpen: true,
+ });
+
+ expect(getByTestId('edit field')).toBeInTheDocument();
+ expect(queryByTestId('section-card-header__expanded-btn')).not.toBeInTheDocument();
+ expect(queryByTestId('edit-button')).not.toBeInTheDocument();
+ });
+
+ it('check is field disabled when isDisabledEditField is true', () => {
+ const { getByTestId } = renderComponent({
+ ...cardHeaderProps,
+ isFormOpen: true,
+ isDisabledEditField: true,
+ });
+
+ expect(getByTestId('edit field')).toBeDisabled();
+ });
+
+ it('calls onClickDelete when item is clicked', () => {
+ const { getByText, getByTestId } = renderComponent();
+
+ const menuButton = getByTestId('section-card-header__menu-button');
+ fireEvent.click(menuButton);
+
+ const deleteMenuItem = getByText(messages.menuDelete.defaultMessage);
+ fireEvent.click(deleteMenuItem);
+ expect(onClickDeleteMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls onClickDuplicate when item is clicked', () => {
+ const { getByText, getByTestId } = renderComponent();
+
+ const menuButton = getByTestId('section-card-header__menu-button');
+ fireEvent.click(menuButton);
+
+ const duplicateMenuItem = getByText(messages.menuDuplicate.defaultMessage);
+ fireEvent.click(duplicateMenuItem);
+ expect(onClickDuplicateMock).toHaveBeenCalled();
+ });
+});
diff --git a/src/course-outline/card-header/messages.js b/src/course-outline/card-header/messages.js
new file mode 100644
index 000000000..a08e1eaa6
--- /dev/null
+++ b/src/course-outline/card-header/messages.js
@@ -0,0 +1,46 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ expandTooltip: {
+ id: 'course-authoring.course-outline.section.expandTooltip',
+ defaultMessage: 'Collapse/Expand this section',
+ },
+ statusBadgeLive: {
+ id: 'course-authoring.course-outline.section.status-badge.live',
+ defaultMessage: 'Live',
+ },
+ statusBadgePublishedNotLive: {
+ id: 'course-authoring.course-outline.section.status-badge.published-not-live',
+ defaultMessage: 'Published not live',
+ },
+ statusBadgeStuffOnly: {
+ id: 'course-authoring.course-outline.section.status-badge.staff-only',
+ defaultMessage: 'Staff only',
+ },
+ statusBadgeDraft: {
+ id: 'course-authoring.course-outline.section.status-badge.draft',
+ defaultMessage: 'Draft',
+ },
+ altButtonEdit: {
+ id: 'course-authoring.course-outline.section.button.edit.alt',
+ defaultMessage: 'Edit',
+ },
+ menuPublish: {
+ id: 'course-authoring.course-outline.section.menu.publish',
+ defaultMessage: 'Publish',
+ },
+ menuConfigure: {
+ id: 'course-authoring.course-outline.section.menu.configure',
+ defaultMessage: 'Configure',
+ },
+ menuDuplicate: {
+ id: 'course-authoring.course-outline.section.menu.duplicate',
+ defaultMessage: 'Duplicate',
+ },
+ menuDelete: {
+ id: 'course-authoring.course-outline.section.menu.delete',
+ defaultMessage: 'Delete',
+ },
+});
+
+export default messages;
diff --git a/src/course-outline/constants.js b/src/course-outline/constants.js
index cd4c58eeb..b57569672 100644
--- a/src/course-outline/constants.js
+++ b/src/course-outline/constants.js
@@ -1,3 +1,14 @@
+export const SECTION_BADGE_STATUTES = {
+ live: 'live',
+ publishedNotLive: 'published_not_live',
+ staffOnly: 'staff_only',
+ draft: 'draft',
+};
+
+export const STAFF_ONLY = 'staff_only';
+
+export const HIGHLIGHTS_FIELD_MAX_LENGTH = 250;
+
export const CHECKLIST_FILTERS = {
ALL: 'ALL',
SELF_PACED: 'SELF_PACED',
diff --git a/src/course-outline/data/api.js b/src/course-outline/data/api.js
index 9b613bf81..7e8035c49 100644
--- a/src/course-outline/data/api.js
+++ b/src/course-outline/data/api.js
@@ -24,6 +24,9 @@ export const getEnableHighlightsEmailsApiUrl = (courseId) => {
};
export const getCourseReindexApiUrl = (reindexLink) => `${getApiBaseUrl()}${reindexLink}`;
+export const getUpdateCourseSectionApiUrl = (sectionId) => `${getApiBaseUrl()}/xblock/${sectionId}`;
+export const getCourseSectionApiUrl = (sectionId) => `${getApiBaseUrl()}/xblock/outline/${sectionId}`;
+export const getCourseSectionDuplicateApiUrl = () => `${getApiBaseUrl()}/xblock/`;
/**
* Get course outline index.
@@ -105,3 +108,91 @@ export async function restartIndexingOnCourse(reindexLink) {
return camelCaseObject(data);
}
+
+/**
+ * Get course section
+ * @param {string} sectionId
+ * @returns {Promise