Skip to content

Commit

Permalink
Merge pull request #1 from Pearson-Advance/vue/PADV-256
Browse files Browse the repository at this point in the history
PADV-256 - Integrate current Sidebar MFE into the Learning Site.
  • Loading branch information
anfbermudezme authored Dec 22, 2022
2 parents 17a102d + 4e50c7c commit 2d6a041
Show file tree
Hide file tree
Showing 23 changed files with 311 additions and 35 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ CREDIT_HELP_LINK_URL=''
CSRF_TOKEN_API_PATH=''
DISCOVERY_API_BASE_URL=''
DISCUSSIONS_MFE_BASE_URL=''
SIDEBAR_MFE_BASE_URL=''
ECOMMERCE_BASE_URL=''
ENABLE_JUMPNAV='true'
ENABLE_NOTICES=''
Expand Down
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ CREDIT_HELP_LINK_URL='https://edx.readthedocs.io/projects/edx-guide-for-students
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
SIDEBAR_MFE_BASE_URL='http://localhost:9090'
ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true'
ENABLE_NOTICES=''
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ CREDIT_HELP_LINK_URL='https://edx.readthedocs.io/projects/edx-guide-for-students
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
SIDEBAR_MFE_BASE_URL='http://localhost:9090'
ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true'
ENABLE_NOTICES=''
Expand Down
10 changes: 10 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
const { createConfig } = require('@edx/frontend-build');

module.exports = createConfig('eslint', {
rules: {
'import/no-unresolved': 'off',
},
'settings': {
'import/resolver': {
'node': {
'paths': ['src'],
},
},
},
overrides: [{
files: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)", "setupTest.js"],
rules: {
Expand Down
8 changes: 2 additions & 6 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
name: validate
on:
push:
branches:
- master
branches: [pearson-release/olive.master]
pull_request:
branches:
- '**'
branches: [pearson-release/olive.master]
jobs:
tests:
runs-on: ubuntu-latest
Expand All @@ -20,5 +18,3 @@ jobs:
- run: make validate.ci
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
13 changes: 13 additions & 0 deletions docs/decisions/0010-outline-sidebar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Add Outline Sidebar

Following the [DISCOVERY](https://agile-jira.pearson.com/browse/PADV-213) made and the proposed [DESIGN](https://lucid.app/lucidchart/d52cc785-409f-4964-af29-ff277baa5bc5/edit?invitationId=inv_e569412e-e9f0-44aa-a9fa-54123d272f1a&referringApp=slack&page=l2M~LaFs47mo#), the MFE sidebar navigation is added in Frontend-App-Learning.

Using currently logic in Frontend-App-Learning, **Outline Sidebar** is integrated following Discussion Sidebar. Using the already created **SidebarBase** function, the integration of the Outline sidebar is done through an iframe, which allows the integration of a Microfrontend located in a certain path, in this case using the port :9090, called **SIDEBAR_MFE_BASE_URL**.

Then, using **SidebarTriggerBase**, the respective integration of the trigger is done, where to locate it in a different div from the one that already exists in src/courseware/course/Course.jsx module, the **SidebarOutlineTrigger** module is created.

To show the Outline Sidebar it is necessary to take the context through the **SidebarContext** function to src/courseware/course/sequence/Sequence.jsx where if the Outline Sidebar is active it will be shown on the left, otherwise another sidebar will be shown in the default from the platform.

## Next Step: Add Navigation Functionality

The next step for the Outline Sidebar integration is to make the navigation more user friendly where it goes to the subsection the user needs without the need to open another tab.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { createConfig } = require('@edx/frontend-build');

module.exports = createConfig('jest', {
modulePaths: ['src'],
setupFilesAfterEnv: [
'<rootDir>/src/setupTest.js',
],
Expand Down
10 changes: 8 additions & 2 deletions src/courseware/course/Course.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ContentTools from './content-tools';
import CourseBreadcrumbs from './CourseBreadcrumbs';
import SidebarProvider from './sidebar/SidebarContextProvider';
import SidebarTriggers from './sidebar/SidebarTriggers';
import SidebarOutlineTrigger from './sidebar/SidebarOutlineTrigger';

import { useModel } from '../../generic/model-store';
import { getSessionStorage, setSessionStorage } from '../../data/sessionStorage';
Expand Down Expand Up @@ -79,7 +80,7 @@ function Course({
<Helmet>
<title>{`${pageTitleBreadCrumbs.join(' | ')} | ${getConfig().SITE_NAME}`}</title>
</Helmet>
<div className="position-relative d-flex align-items-start">
<section className="position-relative d-flex align-items-start">
<CourseBreadcrumbs
courseId={courseId}
sectionId={section ? section.id : null}
Expand All @@ -92,7 +93,12 @@ function Course({
{shouldDisplayTriggers && (
<SidebarTriggers />
)}
</div>
</section>
<section className="d-flex mr-auto p-2">
{shouldDisplayTriggers && (
<SidebarOutlineTrigger />
)}
</section>

<AlertList topic="sequence" />
<Sequence
Expand Down
12 changes: 10 additions & 2 deletions src/courseware/course/sequence/Sequence.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-use-before-define */
import React, {
useEffect, useState,
useEffect, useState, useContext,
} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
Expand All @@ -14,6 +14,8 @@ import { history } from '@edx/frontend-platform';
import SequenceExamWrapper from '@edx/frontend-lib-special-exams';
import { breakpoints, useWindowSize } from '@edx/paragon';

import SidebarContext from 'courseware/course/sidebar/SidebarContext';
import { ID } from 'courseware/course/sidebar/sidebars/outline/OutlineTrigger';
import PageLoading from '../../../generic/PageLoading';
import { useModel } from '../../../generic/model-store';
import { useSequenceBannerTextAlert, useSequenceEntranceExamAlert } from '../../../alerts/sequence-alerts/hooks';
Expand Down Expand Up @@ -148,8 +150,14 @@ function Sequence({
history.push(`/course/${courseId}/course-end`);
};

const {
currentSidebar,
} = useContext(SidebarContext);
const isOutlineActive = currentSidebar === ID;

const defaultContent = (
<div className="sequence-container d-inline-flex flex-row">
{isOutlineActive ? <Sidebar /> : null}
<div className={classNames('sequence w-100', { 'position-relative': shouldDisplayNotificationTriggerInSequence })}>
<SequenceNavigation
sequenceId={sequenceId}
Expand Down Expand Up @@ -202,7 +210,7 @@ function Sequence({
)}
</div>
</div>
<Sidebar />
{isOutlineActive ? null : <Sidebar />}

{/** [MM-P2P] Experiment */}
{(mmp2p.state.isEnabled && mmp2p.flyover.isVisible) && (
Expand Down
15 changes: 15 additions & 0 deletions src/courseware/course/sidebar/SidebarOutlineTrigger.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import TriggerButton from 'courseware/course/sidebar/TriggerButton';
import { ID } from 'courseware/course/sidebar/sidebars/outline/OutlineTrigger';

function SidebarOutlineTrigger() {
return (
<section className="d-flex mb-auto">
<TriggerButton sidebarId={ID} />
</section>
);
}

SidebarOutlineTrigger.propTypes = {};

export default SidebarOutlineTrigger;
31 changes: 8 additions & 23 deletions src/courseware/course/sidebar/SidebarTriggers.jsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
import classNames from 'classnames';
import React, { useContext } from 'react';
import SidebarContext from './SidebarContext';
import { SIDEBAR_ORDER, SIDEBARS } from './sidebars';
import React from 'react';
import { SIDEBAR_ORDER } from 'courseware/course/sidebar/sidebars';
import TriggerButton from 'courseware/course/sidebar/TriggerButton';

function SidebarTriggers() {
const {
toggleSidebar,
currentSidebar,
} = useContext(SidebarContext);
return (
<div className="d-flex ml-auto">
{SIDEBAR_ORDER.map((sidebarId) => {
const { Trigger } = SIDEBARS[sidebarId];
const isActive = sidebarId === currentSidebar;
return (
<div
className={classNames('mt-3', { 'border-primary-700': isActive })}
style={{ borderBottom: isActive ? '2px solid' : null }}
key={sidebarId}
>
<Trigger onClick={() => toggleSidebar(sidebarId)} key={sidebarId} />
</div>
);
})}
</div>
<section className="d-flex ml-auto">
{SIDEBAR_ORDER.map((sidebarId) => (
<TriggerButton sidebarId={sidebarId} />
))}
</section>
);
}

Expand Down
31 changes: 31 additions & 0 deletions src/courseware/course/sidebar/TriggerButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import SidebarContext from './SidebarContext';
import { SIDEBARS } from './sidebars';

function TriggerButton({
sidebarId,
}) {
const {
toggleSidebar,
currentSidebar,
} = useContext(SidebarContext);
const { Trigger } = SIDEBARS[sidebarId];
const isActive = sidebarId === currentSidebar;
return (
<section
className={classNames('mt-3', { 'border-primary-700': isActive })}
style={{ borderBottom: isActive ? '2px solid' : null }}
key={sidebarId}
>
<Trigger onClick={() => toggleSidebar(sidebarId)} key={sidebarId} />
</section>
);
}

TriggerButton.propTypes = {
sidebarId: PropTypes.string.isRequired,
};

export default TriggerButton;
10 changes: 8 additions & 2 deletions src/courseware/course/sidebar/sidebars/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import * as notifications from './notifications';
import * as discusssions from './discussions';
import * as outline from 'courseware/course/sidebar/sidebars/outline';
import * as notifications from 'courseware/course/sidebar/sidebars/notifications';
import * as discusssions from 'courseware/course/sidebar/sidebars/discussions';

export const SIDEBARS = {
[outline.ID]: {
ID: outline.ID,
Sidebar: outline.Sidebar,
Trigger: outline.Trigger,
},
[notifications.ID]: {
ID: notifications.ID,
Sidebar: notifications.Sidebar,
Expand Down
43 changes: 43 additions & 0 deletions src/courseware/course/sidebar/sidebars/outline/OutlineSidebar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ensureConfig, getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import React, { useContext } from 'react';
import SidebarBase from 'courseware/course/sidebar/common/SidebarBase';
import SidebarContext from 'courseware/course/sidebar/SidebarContext';
import { ID } from 'courseware/course/sidebar/sidebars/outline/OutlineTrigger';

import messages from './messages';

ensureConfig(['SIDEBAR_MFE_BASE_URL']);

function OutlineSidebar({ intl }) {
const {
courseId,
} = useContext(SidebarContext);

const outlineUrl = `${getConfig().SIDEBAR_MFE_BASE_URL}/${courseId}`;
return (
<SidebarBase
title={intl.formatMessage(messages.outlineTitle)}
ariaLabel={intl.formatMessage(messages.outlineTitle)}
sidebarId={ID}
width="40rem"
showTitleBar={false}
>
<iframe
src={`${outlineUrl}?inContext`}
className="d-flex w-100 border-0"
style={{ minHeight: '60rem' }}
title={intl.formatMessage(messages.outlineTitle)}
/>
</SidebarBase>
);
}

OutlineSidebar.propTypes = {
intl: intlShape.isRequired,
};

OutlineSidebar.Trigger = OutlineSidebar;
OutlineSidebar.ID = ID;

export default injectIntl(OutlineSidebar);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import {
initializeMockApp, initializeTestStore, render, screen,
} from 'setupTest';
import SidebarContext from 'courseware/course/sidebar/SidebarContext';
import OutlineSidebar from 'courseware/course/sidebar/sidebars/outline/OutlineSidebar';

initializeMockApp();

describe('Outline Sidebar', () => {
let mockData;
let courseId;

beforeEach(async () => {
const store = await initializeTestStore({
excludeFetchCourse: false,
excludeFetchSequence: false,
});
const state = store.getState();
courseId = state.courseware.courseId;

mockData = {
courseId,
currentSidebar: 'OUTLINE',
};
});

function renderWithProvider(testData = {}) {
const { container } = render(
<SidebarContext.Provider value={{ ...mockData, ...testData }}>
<OutlineSidebar />
</SidebarContext.Provider>,
);
return container;
}

it('should show up if courseId associated with it', async () => {
renderWithProvider();
expect(screen.queryByTitle('Outline')).toBeInTheDocument();
expect(screen.queryByTitle('Outline'))
.toHaveAttribute('src', `http://localhost:9090/${courseId}?inContext`);
});
});
29 changes: 29 additions & 0 deletions src/courseware/course/sidebar/sidebars/outline/OutlineTrigger.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ensureConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Icon } from '@edx/paragon';
import { ExpandMore } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import React from 'react';
import SidebarTriggerBase from 'courseware/course/sidebar/common/TriggerBase';
import messages from 'courseware/course/sidebar/sidebars/outline/messages';

ensureConfig(['SIDEBAR_MFE_BASE_URL']);
export const ID = 'OUTLINE';

function OutlineTrigger({
intl,
onClick,
}) {
return (
<SidebarTriggerBase onClick={onClick} ariaLabel={intl.formatMessage(messages.openOutlineTrigger)}>
<Icon src={ExpandMore} className="m-0 m-auto" />
</SidebarTriggerBase>
);
}

OutlineTrigger.propTypes = {
intl: intlShape.isRequired,
onClick: PropTypes.func.isRequired,
};

export default injectIntl(OutlineTrigger);
Loading

0 comments on commit 2d6a041

Please sign in to comment.