Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=false
ENABLE_TAGGING_TAXONOMY_PAGES=true
ENABLE_CERTIFICATE_PAGE=true
ENABLE_COURSE_IMPORT_IN_LIBRARY=false
ENABLE_COURSE_OUTLINE_NEW_DESIGN=false
BBB_LEARN_MORE_URL=''
HOTJAR_APP_ID=''
HOTJAR_VERSION=6
Expand Down
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ ENABLE_ASSETS_PAGE=false
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=true
ENABLE_CERTIFICATE_PAGE=true
ENABLE_COURSE_IMPORT_IN_LIBRARY=true
ENABLE_COURSE_OUTLINE_NEW_DESIGN=true
ENABLE_NEW_VIDEO_UPLOAD_PAGE=true
ENABLE_TAGGING_TAXONOMY_PAGES=true
BBB_LEARN_MORE_URL=''
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ENABLE_ASSETS_PAGE=false
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=true
ENABLE_CERTIFICATE_PAGE=true
ENABLE_COURSE_IMPORT_IN_LIBRARY=true
ENABLE_COURSE_OUTLINE_NEW_DESIGN=false
ENABLE_TAGGING_TAXONOMY_PAGES=true
BBB_LEARN_MORE_URL=''
INVITE_STUDENTS_EMAIL_TO="[email protected]"
Expand Down
4 changes: 2 additions & 2 deletions src/course-libraries/CourseLibraries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,15 @@ export const CourseLibraries = () => {
<SubHeader
title={intl.formatMessage(messages.headingTitle)}
subtitle={intl.formatMessage(messages.headingSubtitle)}
headerActions={!showReviewAlert && outOfSyncCount > 0 && tabKey === CourseLibraryTabs.all && (
headerActions={(!showReviewAlert && outOfSyncCount > 0 && tabKey === CourseLibraryTabs.all) ? (
<Button
variant="primary"
onClick={onAlertReview}
iconBefore={Cached}
>
{intl.formatMessage(messages.reviewUpdatesBtn)}
</Button>
)}
) : null}
hideBorder
/>
<section className="mb-4">
Expand Down
27 changes: 21 additions & 6 deletions src/course-outline/CourseOutline.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getConfig } from '@edx/frontend-platform';
import { getConfig, setConfig } from '@edx/frontend-platform';
import { cloneDeep } from 'lodash';
import { closestCorners } from '@dnd-kit/core';
import { logError } from '@edx/frontend-platform/logging';
Expand All @@ -17,6 +17,7 @@ import {
act, fireEvent, initializeMocks, render, screen, waitFor, within,
} from '@src/testUtils';
import { XBlock } from '@src/data/types';
import { userEvent } from '@testing-library/user-event';
import {
getCourseBestPracticesApiUrl,
getCourseLaunchApiUrl,
Expand Down Expand Up @@ -182,12 +183,10 @@ describe('<CourseOutline />', () => {
});

it('render CourseOutline component correctly', async () => {
const { getByText } = renderComponent();
renderComponent();

await waitFor(() => {
expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument();
});
expect(await screen.findByText('Demonstration Course')).toBeInTheDocument();
expect(await screen.findByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument();
});

it('logs an error when syncDiscussionsTopics encounters an API failure', async () => {
Expand Down Expand Up @@ -2486,4 +2485,20 @@ describe('<CourseOutline />', () => {
});
expect(axiosMock.history.delete[0].url).toBe(getDownstreamApiUrl(courseSectionMock.id));
});

it('check that the new status bar and expand bar is shown when flag is set', async () => {
setConfig({
...getConfig(),
ENABLE_COURSE_OUTLINE_NEW_DESIGN: 'true',
});
renderComponent();
const btn = await screen.findByRole('button', { name: 'Collapse all' });
expect(btn).toBeInTheDocument();
expect(await screen.findByRole('link', { name: 'View live' })).toBeInTheDocument();
expect(await screen.findByRole('button', { name: 'Add' })).toBeInTheDocument();
expect(await screen.findByRole('button', { name: 'More actions' })).toBeInTheDocument();
const user = userEvent.setup();
await user.click(btn);
expect(await screen.findByRole('button', { name: 'Expand all' })).toBeInTheDocument();
});
});
56 changes: 46 additions & 10 deletions src/course-outline/CourseOutline.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { useState, useEffect, useCallback } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import {
Container,
Layout,
Row,
TransitionReplace,
Toast,
StandardModal,
Button,
ActionRow,
} from '@openedx/paragon';
import { Helmet } from 'react-helmet';
import { CheckCircle as CheckCircleIcon } from '@openedx/paragon/icons';
import { CheckCircle as CheckCircleIcon, CloseFullscreen, OpenInFull } from '@openedx/paragon/icons';
import { useSelector } from 'react-redux';
import {
arrayMove,
Expand Down Expand Up @@ -44,7 +47,6 @@ import {
getTimedExamsFlag,
} from './data/selectors';
import { COURSE_BLOCK_NAMES } from './constants';
import StatusBar from './status-bar/StatusBar';
import EnableHighlightsModal from './enable-highlights-modal/EnableHighlightsModal';
import SectionCard from './section-card/SectionCard';
import SubsectionCard from './subsection-card/SubsectionCard';
Expand All @@ -61,8 +63,11 @@ import {
} from './drag-helper/utils';
import { useCourseOutline } from './hooks';
import messages from './messages';
import headerMessages from './header-navigations/messages';
import { getTagsExportFile } from './data/api';
import OutlineAddChildButtons from './OutlineAddChildButtons';
import { StatusBar } from './status-bar/StatusBar';
import { LegacyStatusBar } from './status-bar/LegacyStatusBar';

const CourseOutline = () => {
const intl = useIntl();
Expand Down Expand Up @@ -141,6 +146,9 @@ const CourseOutline = () => {
resetScrollState,
} = useCourseOutline({ courseId });

// Show the new actions bar if it is enabled in the configuration.
// This is a temporary flag until the new design feature is fully implemented.
const showNewActionsBar = getConfig().ENABLE_COURSE_OUTLINE_NEW_DESIGN?.toString().toLowerCase() === 'true';
// Use `setToastMessage` to show the toast.
const [toastMessage, setToastMessage] = useState<string | null>(null);

Expand Down Expand Up @@ -314,8 +322,9 @@ const CourseOutline = () => {
) : null}
</TransitionReplace>
<SubHeader
title={intl.formatMessage(messages.headingTitle)}
title={courseName}
subtitle={intl.formatMessage(messages.headingSubtitle)}
hideBorder
headerActions={(
<CourseOutlineHeaderActionsSlot
isReIndexShow={isReIndexShow}
Expand All @@ -329,6 +338,23 @@ const CourseOutline = () => {
/>
)}
/>
{showNewActionsBar
? (
<StatusBar
courseId={courseId}
isLoading={isLoading}
statusBarData={statusBarData}
/>
) : (
<LegacyStatusBar
courseId={courseId}
isLoading={isLoading}
statusBarData={statusBarData}
openEnableHighlightsModal={openEnableHighlightsModal}
handleVideoSharingOptionChange={handleVideoSharingOptionChange}
/>
)}
<hr className="mt-4 mb-0 w-100 text-light-400" />
<Layout
lg={[{ span: 9 }, { span: 3 }]}
md={[{ span: 9 }, { span: 3 }]}
Expand All @@ -339,14 +365,24 @@ const CourseOutline = () => {
<Layout.Element>
<article>
<div>
{showNewActionsBar && (
<ActionRow className="mt-3">
{Boolean(sectionsList.length) && (
<Button
variant="outline-primary"
id="expand-collapse-all-button"
data-testid="expand-collapse-all-button"
iconBefore={isSectionsExpanded ? CloseFullscreen : OpenInFull}
onClick={headerNavigationsActions.handleExpandAll}
>
{isSectionsExpanded
? intl.formatMessage(headerMessages.collapseAllButton)
: intl.formatMessage(headerMessages.expandAllButton)}
</Button>
)}
</ActionRow>
)}
<section className="course-outline-section">
<StatusBar
courseId={courseId}
isLoading={isLoading}
statusBarData={statusBarData}
openEnableHighlightsModal={openEnableHighlightsModal}
handleVideoSharingOptionChange={handleVideoSharingOptionChange}
/>
{!errors?.outlineIndexApi && (
<div className="pt-4">
{sections.length ? (
Expand Down
1 change: 1 addition & 0 deletions src/course-outline/data/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const initialState = {
savingStatus: '',
statusBarData: {
courseReleaseDate: '',
endDate: '',
highlightsEnabledForMessaging: false,
isSelfPaced: false,
checklist: {
Expand Down
2 changes: 2 additions & 0 deletions src/course-outline/data/thunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export function fetchCourseOutlineIndexQuery(courseId: string): (dispatch: any)
videoSharingEnabled,
videoSharingOptions,
actions,
end,
},
} = outlineIndex;
dispatch(fetchOutlineIndexSuccess(outlineIndex));
Expand All @@ -83,6 +84,7 @@ export function fetchCourseOutlineIndexQuery(courseId: string): (dispatch: any)
highlightsEnabledForMessaging,
videoSharingOptions,
videoSharingEnabled,
endDate: end,
}));
dispatch(updateCourseActions(actions));

Expand Down
31 changes: 18 additions & 13 deletions src/course-outline/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export interface CourseStructure {
highlightsEnabledForMessaging: boolean,
videoSharingEnabled: boolean,
videoSharingOptions: string,
start: string,
end: string,
actions: XBlockActions,
}

Expand Down Expand Up @@ -33,6 +35,21 @@ export interface CourseDetails {
description?: string;
}

export interface CourseOutlineStatusBar {
courseReleaseDate: string;
endDate: string;
highlightsEnabledForMessaging: boolean;
isSelfPaced: boolean;
checklist: {
totalCourseLaunchChecks: number;
completedCourseLaunchChecks: number;
totalCourseBestPracticesChecks: number;
completedCourseBestPracticesChecks: number;
};
videoSharingEnabled: boolean;
videoSharingOptions: string;
}

export interface CourseOutlineState {
loadingStatus: {
outlineIndexLoadingStatus: string;
Expand All @@ -48,19 +65,7 @@ export interface CourseOutlineState {
};
outlineIndexData: object;
savingStatus: string;
statusBarData: {
courseReleaseDate: string;
highlightsEnabledForMessaging: boolean;
isSelfPaced: boolean;
checklist: {
totalCourseLaunchChecks: number;
completedCourseLaunchChecks: number;
totalCourseBestPracticesChecks: number;
completedCourseBestPracticesChecks: number;
};
videoSharingEnabled: boolean;
videoSharingOptions: string;
};
statusBarData: CourseOutlineStatusBar;
sectionsList: Array<XBlock>;
isCustomRelativeDatesActive: boolean;
currentSection: XBlock | {};
Expand Down
58 changes: 58 additions & 0 deletions src/course-outline/header-navigations/HeaderActions.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
fireEvent, initializeMocks, render, screen,
} from '@src/testUtils';
import messages from './messages';
import HeaderActions, { HeaderActionsProps } from './HeaderActions';

const handleNewSectionMock = jest.fn();

const headerNavigationsActions = {
handleNewSection: handleNewSectionMock,
lmsLink: '',
};

const courseActions = {
draggable: true,
childAddable: true,
deletable: true,
duplicable: true,
};

const renderComponent = (props?: Partial<HeaderActionsProps>) => render(
<HeaderActions
actions={headerNavigationsActions}
courseActions={courseActions}
{...props}
/>,
);

describe('<HeaderActions />', () => {
beforeEach(() => {
initializeMocks();
});

it('render HeaderActions component correctly', async () => {
renderComponent();

expect(await screen.findByRole('button', { name: messages.addButton.defaultMessage })).toBeInTheDocument();
expect(await screen.findByRole('button', { name: messages.viewLiveButton.defaultMessage })).toBeInTheDocument();
expect(await screen.findByRole('button', { name: messages.moreActionsButtonAriaLabel.defaultMessage })).toBeInTheDocument();
});

it('calls the correct handlers when clicking buttons', async () => {
renderComponent();

const addButton = await screen.findByRole('button', { name: messages.addButton.defaultMessage });
fireEvent.click(addButton);
expect(handleNewSectionMock).toHaveBeenCalledTimes(1);
});

it('disables new section button if course outline fetch fails', async () => {
renderComponent({
errors: { outlineIndexApi: { data: 'some error', type: 'serverError' } },
});

expect(await screen.findByRole('button', { name: messages.addButton.defaultMessage })).toBeInTheDocument();
expect(await screen.findByRole('button', { name: messages.addButton.defaultMessage })).toBeDisabled();
});
});
Loading