Compare commits

..

1 Commits

Author SHA1 Message Date
mubbsharanwar
6162174be8 chore: upgrade frontend-component-header version
upgrade frontend-component-header version to enable show/hide username from header based on the flag

VAN-1804
2024-02-06 20:40:57 +05:00
324 changed files with 18078 additions and 10952 deletions

2
.env
View File

@@ -4,7 +4,6 @@
NODE_ENV='production'
ACCESS_TOKEN_COOKIE_NAME=''
APP_ID='learning'
BASE_URL=''
CONTACT_URL=''
CREDENTIALS_BASE_URL=''
@@ -14,6 +13,7 @@ DISCOVERY_API_BASE_URL=''
DISCUSSIONS_MFE_BASE_URL=''
ECOMMERCE_BASE_URL=''
ENABLE_JUMPNAV='true'
ENABLE_NEW_SIDEBAR=''
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
EXAMS_BASE_URL=''

View File

@@ -4,7 +4,6 @@
NODE_ENV='development'
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
APP_ID='learning'
BASE_URL='http://localhost:2000'
CONTACT_URL='http://localhost:18000/contact'
CREDENTIALS_BASE_URL='http://localhost:18150'
@@ -14,6 +13,7 @@ DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true'
ENABLE_NEW_SIDEBAR=''
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
EXAMS_BASE_URL=''

View File

@@ -4,7 +4,6 @@
NODE_ENV='test'
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
APP_ID='learning'
BASE_URL='http://localhost:2000'
CONTACT_URL='http://localhost:18000/contact'
CREDENTIALS_BASE_URL='http://localhost:18150'
@@ -14,6 +13,7 @@ DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true'
ENABLE_NEW_SIDEBAR=''
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
EXAMS_BASE_URL='http://localhost:18740'

View File

@@ -3,5 +3,3 @@ dist/
packages/
node_modules/
jest.config.js
env.config.jsx
example.env.config.jsx

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@openedx/frontend-build');
const { createConfig } = require('@edx/frontend-build');
const config = createConfig('eslint', {
rules: {
@@ -12,13 +12,6 @@ const config = createConfig('eslint', {
'react/no-unknown-property': 'off',
'func-names': 'off',
},
settings: {
'import/resolver': {
webpack: {
config: 'webpack.prod.config.js',
},
},
},
});
module.exports = config;

View File

@@ -1,7 +0,0 @@
version: 2
updates:
# Adding new check for github-actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -9,35 +9,15 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
node: [18, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: make validate.ci
- name: Archive code coverage results
uses: actions/upload-artifact@v4
with:
name: code-coverage-report-${{ matrix.node }}
# When we're only using Node 20, replace the line above with the following:
# name: code-coverage-report
path: coverage/*.*
coverage:
runs-on: ubuntu-latest
needs: tests
steps:
- uses: actions/checkout@v3
- name: Download code coverage results
uses: actions/download-artifact@v4
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- uses: actions/setup-node@v3
with:
name: code-coverage-report-20
# When we're only using Node 20, replace the line above with the following:
# name: code-coverage-report
node-version: ${{ env.NODE_VER }}
- run: make validate.ci
- name: Upload coverage
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

5
.gitignore vendored
View File

@@ -6,7 +6,6 @@
node_modules
npm-debug.log
coverage
env.config.*
dist/
src/i18n/transifex_input.json
@@ -26,7 +25,3 @@ module.config.js
# Local environment overrides
.env.private
src/i18n/messages/
env.config.jsx

2
.nvmrc
View File

@@ -1 +1 @@
20
18

9
.tx/config Normal file
View File

@@ -0,0 +1,9 @@
[main]
host = https://www.transifex.com
[o:open-edx:p:edx-platform:r:frontend-app-learning]
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = KEYVALUEJSON

View File

@@ -1,17 +1,20 @@
export TRANSIFEX_RESOURCE=frontend-app-learning
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fa_IR,fr_CA,it_IT,pt_PT,de_DE"
intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-formatjs
transifex_temp = ./temp/babel-plugin-react-intl
precommit:
npm run lint
npm audit
requirements:
npm ci
npm install
i18n.extract:
# Pulling display strings from .jsx files into .json files...
@@ -29,21 +32,37 @@ detect_changed_source_translations:
# Checking for changed translations...
git diff --exit-code $(i18n)
# Pushes translations to Transifex. You must run make extract_translations first.
push_translations:
# Pushing strings to Transifex...
tx push -s
# Fetching hashes from Transifex...
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
# Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
# Pushing comments to Transifex...
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
ifeq ($(OPENEDX_ATLAS_PULL),)
# Pulls translations from Transifex.
pull_translations:
tx pull -f --mode reviewed --languages=$(transifex_langs)
else
# Experimental: OEP-58 Pulls translations using atlas
pull_translations:
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull $(ATLAS_OPTIONS) \
translations/frontend-platform/src/i18n/messages:frontend-platform \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-lib-special-exams/src/i18n/messages:frontend-lib-special-exams \
translations/frontend-app-learning/src/i18n/messages:frontend-app-learning
&& atlas pull $(ATLAS_OPTIONS) \
translations/frontend-platform/src/i18n/messages:frontend-platform \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-lib-special-exams/src/i18n/messages:frontend-lib-special-exams \
translations/frontend-app-learning/src/i18n/messages:frontend-app-learning
$(intl_imports) frontend-platform paragon frontend-component-header frontend-component-footer frontend-lib-special-exams frontend-app-learning
endif
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:
@@ -55,10 +74,8 @@ validate:
make validate-no-uncommitted-package-lock-changes
npm run i18n_extract
npm run lint -- --max-warnings 0
npm run types
npm run test
npm run build
npm run bundlewatch
.PHONY: validate.ci
validate.ci:

View File

@@ -1,110 +1,77 @@
frontend-app-learning
#####################
frontend-app-learning
######################
|codecov| |license|
********
Purpose
*******
********
This is the Learning MFE (micro-frontend application), which renders all
learner-facing course pages (like the course outline, the progress page,
actual course content, etc).
Please tag **@edx/engage-squad** on any PRs or issues. Thanks.
.. |codecov| image:: https://codecov.io/gh/edx/frontend-app-learning/branch/master/graph/badge.svg?token=3z7XvuzTq3
:target: https://codecov.io/gh/edx/frontend-app-learning
.. |license| image:: https://img.shields.io/badge/license-AGPL-informational
:target: https://github.com/openedx/frontend-app-account/blob/master/LICENSE
***************
Getting Started
***************
Prerequisites
=============
`Tutor`_ is currently recommended as a development environment for the Learning
MFE. Most likely, it already has this MFE configured; however, you'll need to
make some changes in order to run it in development mode. You can refer
to the `relevant tutor-mfe documentation`_ for details, or follow the quick
guide below.
The `devstack`_ is currently recommended as a development environment for your
new MFE. If you start it with ``make dev.up.lms`` that should give you
everything you need as a companion to this frontend.
Note that it is also possible to use `Tutor`_ to develop an MFE. You can refer
to the `relevant tutor-mfe documentation`_ to get started using it.
.. _Devstack: https://github.com/openedx/devstack
.. _Tutor: https://github.com/overhangio/tutor
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe#mfe-development
To use this application, `devstack <https://github.com/openedx/devstack>`__ must be running and you must be logged into it.
Cloning and Setup
=================
- Visit http://localhost:2000/course/course-v1:edX+DemoX+Demo_Course to view the demo course. You can replace ``course-v1:edX+DemoX+Demo_Course`` with a different course key.
1. Clone your new repo:
Cloning and Startup
===================
.. code-block:: bash
.. code-block::
git clone https://github.com/openedx/frontend-app-learning.git
1. Clone your new repo:
2. Use node v20.x.
``git clone https://github.com/openedx/frontend-app-learning.git``
The current version of the micro-frontend build scripts supports node 18.
Using other major versions of node *may* work, but this is unsupported. For
convenience, this repository includes an ``.nvmrc`` file to help in setting the
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
2. Use node v18.x.
3. Stop the Tutor devstack, if it's running: ``tutor dev stop``
The current version of the micro-frontend build scripts support node 18.
Using other major versions of node *may* work, but this is unsupported. For
convenience, this repository includes an .nvmrc file to help in setting the
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
4. Next, we need to tell Tutor that we're going to be running this repo in
development mode, and it should be excluded from the ``mfe`` container that
otherwise runs every MFE. Run this:
3. Install npm dependencies:
.. code-block:: bash
``cd frontend-app-learning && npm ci``
tutor mounts add /path/to/frontend-app-learning
4. Start the dev server:
5. Start Tutor in development mode. This command will start the LMS and Studio,
and other required MFEs like ``authn`` and ``account``, but will not start
the learning MFE, which we're going to run on the host instead of in a
container managed by Tutor. Run:
.. code-block:: bash
tutor dev start lms cms mfe
Startup
=======
1. Install npm dependencies:
.. code-block:: bash
cd frontend-app-learning && npm ci
2. Start the dev server:
.. code-block:: bash
npm run dev
Then you can access the app at http://local.openedx.io:2000/learning/
Troubleshooting
---------------
If you see an "Invalid Host header" error, then you're probably using a different domain name for your devstack such as
``local.edly.io`` or ``local.overhang.io`` (not the new recommended default, ``local.openedx.io``). In that case, run
these commands to update your devstack's domain names:
.. code-block:: bash
tutor dev stop
tutor config save --set LMS_HOST=local.openedx.io --set CMS_HOST=studio.local.openedx.io
tutor dev launch -I --skip-build
tutor dev stop learning # We will run this MFE on the host
``npm start``
Local module development
=========================
To develop locally on modules that are installed into this app, you'll need to create a ``module.config.js``
file (which is git-ignored) that defines where to find your local modules, for instance:
.. code-block:: js
file (which is git-ignored) that defines where to find your local modules, for instance::
module.exports = {
/*
@@ -117,10 +84,10 @@ file (which is git-ignored) that defines where to find your local modules, for i
may want to use "src" if the module installs React as a peer/dev dependency.
*/
localModules: [
{ moduleName: '@openedx/paragon/scss', dir: '../paragon', dist: 'scss' },
{ moduleName: '@openedx/paragon', dir: '../paragon', dist: 'dist' },
{ moduleName: '@openedx/frontend-enterprise', dir: '../frontend-enterprise', dist: 'src' },
{ moduleName: '@openedx/frontend-platform', dir: '../frontend-platform', dist: 'dist' },
{ moduleName: '@edx/paragon/scss', dir: '../paragon', dist: 'scss' },
{ moduleName: '@edx/paragon', dir: '../paragon', dist: 'dist' },
{ moduleName: '@edx/frontend-enterprise', dir: '../frontend-enterprise', dist: 'src' },
{ moduleName: '@edx/frontend-platform', dir: '../frontend-platform', dist: 'dist' },
],
};
@@ -133,14 +100,8 @@ The Learning MFE is similar to all the other Open edX MFEs. Read the Open
edX Developer Guide's section on
`MFE applications <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html>`_.
Plugins
=======
This MFE can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
The parts of this MFE that can be customized in that manner are documented `here </src/plugin-slots>`_.
Environment Variables
=====================
======================
This MFE is configured via environment variables supplied at build time.
All micro-frontends have a shared set of required environment variables,
@@ -166,7 +127,7 @@ SOCIAL_UTM_MILESTONE_CAMPAIGN
SUPPORT_URL_CALCULATOR_MATH
A link that explains how to use the in-course calculator. You can use the
one in the example below if you don't want to have your own branded version.
one in the example below, if you don't want to have your own branded version.
Example: https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator
@@ -179,7 +140,7 @@ SUPPORT_URL_ID_VERIFICATION
SUPPORT_URL_VERIFIED_CERTIFICATE
A link that explains what a verified certificate is. You can use the
one in the example below if you don't want to have your own branded version.
one in the example below, if you don't want to have your own branded version.
Optional.
Example: https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate
@@ -195,13 +156,13 @@ TWITTER_URL
A link to your Twitter account. The Twitter social-share link won't appear
unless this is set. Optional.
Example: https://twitter.com/openedx
Example: https://twitter.com/edXOnline
Getting Help
============
===========
If you're having trouble, we have `discussion forums`_
where you can connect with others in the community.
If you're having trouble, we have discussion forums at
https://discuss.openedx.org where you can connect with others in the community.
Our real-time conversations are on Slack. You can request a `Slack
invitation`_, then join our `community Slack workspace`_. Because this is a
@@ -219,18 +180,17 @@ For more information about these options, see the `Getting Help`_ page.
.. _community Slack workspace: https://openedx.slack.com/
.. _#wg-frontend channel: https://openedx.slack.com/archives/C04BM6YC7A6
.. _Getting Help: https://openedx.org/community/connect
.. _discussion forums: https://discuss.openedx.org
Contributing
============
Contributions are very welcome. Please read `How To Contribute`_ for details.
Contributions are very welcome. Please read `How To Contribute`_ for details.
.. _How To Contribute: https://openedx.org/r/how-to-contribute
This project is currently accepting all types of contributions, bug fixes,
security fixes, maintenance work, or new features. However, please make sure
to discuss your new feature idea with the maintainers before
to have a discussion about your new feature idea with the maintainers prior to
beginning development to maximize the chances of your change being accepted.
You can start a conversation by creating a new issue on this repo summarizing
your idea.

View File

@@ -1,18 +0,0 @@
# This file records information about this repo. Its use is described in OEP-55:
# https://open-edx-proposals.readthedocs.io/en/latest/processes/oep-0055-proc-project-maintainers.html
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: 'frontend-app-learning'
description: "This is the Learning MFE, which renders all learner-facing course pages."
links:
- url: "https://github.com/openedx/frontend-app-learning"
title: "Learning MFE"
icon: "Web"
annotations:
openedx.org/arch-interest-groups: ""
spec:
owner: group:committers-frontend-app-learning
type: 'website'
lifecycle: 'production'

View File

@@ -1,24 +0,0 @@
import UnitTranslationPlugin from '@edx/unit-translation-selector-plugin';
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';
// Load environment variables from .env file
const config = {
...process.env,
pluginSlots: {
unit_title_plugin: {
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'unit_title_plugin',
type: DIRECT_PLUGIN,
priority: 1,
RenderWidget: UnitTranslationPlugin,
},
},
],
},
},
};
export default config;

View File

@@ -1,6 +1,6 @@
const { createConfig } = require('@openedx/frontend-build');
const { createConfig } = require('@edx/frontend-build');
const config = createConfig('jest', {
module.exports = createConfig('jest', {
setupFilesAfterEnv: [
'<rootDir>/src/setupTest.js',
],
@@ -9,35 +9,13 @@ const config = createConfig('jest', {
'src/i18n',
'src/.*\\.exp\\..*',
],
// see https://github.com/axios/axios/issues/5026
moduleNameMapper: {
"^axios$": "axios/dist/axios.js",
// See https://stackoverflow.com/questions/72382316/jest-encountered-an-unexpected-token-react-markdown
'react-markdown': '<rootDir>/node_modules/react-markdown/react-markdown.min.js',
'@src/(.*)': '<rootDir>/src/$1',
// Explicit mapping to ensure Jest resolves the module correctly
'@edx/frontend-lib-special-exams': '<rootDir>/node_modules/@edx/frontend-lib-special-exams',
},
testTimeout: 30000,
globalSetup: "./global-setup.js",
verbose: true,
testEnvironment: 'jsdom',
globalSetup: "./global-setup.js"
});
// delete config.testURL;
config.reporters = [...(config.reporters || []), ["jest-console-group-reporter", {
// change this setting if need to see less details for each test
// reportType: "summary" | "details",
// enable: true | false,
afterEachTest: {
enable: true,
filePaths: false,
reportType: "details",
},
afterAllTests: {
reportType: "summary",
enable: true,
filePaths: true,
},
}]];
module.exports = config;

14444
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,17 +11,13 @@
],
"scripts": {
"build": "fedx-scripts webpack",
"bundlewatch": "bundlewatch",
"i18n_extract": "fedx-scripts formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx --ext .ts --ext .tsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx --ext .ts --ext .tsx .",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
"prepare": "husky install",
"postinstall": "patch-package",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"dev": "PUBLIC_PATH=/learning/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "fedx-scripts jest --coverage --passWithNoTests",
"types": "tsc --noEmit"
"test": "fedx-scripts jest --coverage --passWithNoTests"
},
"author": "edX",
"license": "AGPL-3.0",
@@ -34,33 +30,27 @@
},
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/browserslist-config": "1.2.0",
"@edx/frontend-component-header": "^5.8.0",
"@edx/frontend-lib-learning-assistant": "^2.2.4",
"@edx/frontend-lib-special-exams": "^3.1.3",
"@edx/frontend-platform": "^8.0.0",
"@edx/frontend-component-footer": "12.2.1",
"@edx/frontend-component-header": "4.6.0",
"@edx/frontend-lib-special-exams": "2.27.0",
"@edx/frontend-lib-learning-assistant": "^1.20.1",
"@edx/frontend-platform": "5.5.2",
"@edx/openedx-atlas": "^0.6.0",
"@edx/react-unit-test-utils": "3.0.0",
"@openedx/paragon": "22.0.0",
"@edx/react-unit-test-utils": "npm:@edx/react-unit-test-utils@1.7.0",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "^0.1.4",
"@openedx/frontend-build": "14.1.2",
"@openedx/frontend-plugin-framework": "^1.2.1",
"@openedx/frontend-slot-footer": "^1.0.2",
"@openedx/paragon": "^22.3.0",
"@popperjs/core": "2.11.8",
"@reduxjs/toolkit": "1.8.1",
"buffer": "^6.0.3",
"classnames": "2.5.1",
"copy-webpack-plugin": "^11.0.0",
"husky": "7.0.4",
"classnames": "2.3.2",
"core-js": "3.22.2",
"history": "5.3.0",
"joi": "^17.11.0",
"js-cookie": "3.0.5",
"lodash": "^4.17.21",
"lodash.camelcase": "4.3.0",
"patch-package": "^8.0.0",
"postcss-loader": "^8.1.1",
"prop-types": "15.8.1",
"query-string": "^7.1.3",
"react": "17.0.2",
@@ -71,34 +61,25 @@
"react-router-dom": "6.15.0",
"react-share": "4.4.1",
"redux": "4.1.2",
"regenerator-runtime": "0.13.11",
"reselect": "4.1.8",
"sass": "^1.79.3",
"sass-loader": "^16.0.2",
"source-map-loader": "^5.0.0",
"truncate-html": "1.0.4"
"truncate-html": "1.0.4",
"util": "0.12.5"
},
"devDependencies": {
"@edx/browserslist-config": "1.2.0",
"@edx/frontend-build": "^12.9.10",
"@edx/reactifex": "2.2.0",
"@pact-foundation/pact": "^13.0.0",
"@testing-library/jest-dom": "5.17.0",
"@pact-foundation/pact": "^11.0.2",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "13.5.0",
"axios-mock-adapter": "2.0.0",
"bundlewatch": "^0.4.0",
"eslint-import-resolver-webpack": "^0.13.9",
"jest": "^29.7.0",
"jest-console-group-reporter": "^1.1.1",
"jest-when": "^3.6.0",
"axios-mock-adapter": "1.20.0",
"copy-webpack-plugin": "^11.0.0",
"es-check": "6.2.1",
"husky": "7.0.4",
"jest": "29.5.0",
"rosie": "2.1.1"
},
"bundlewatch": {
"files": [
{
"path": "dist/*.js",
"maxSize": "1300kB"
}
],
"normalizeFilenames": "^.+?(\\..+?)\\.\\w+$"
}
}

View File

@@ -1,36 +0,0 @@
diff --git a/node_modules/@openedx/frontend-build/config/webpack.prod.config.js b/node_modules/@openedx/frontend-build/config/webpack.prod.config.js
index 2879dd9..9efd0fc 100644
--- a/node_modules/@openedx/frontend-build/config/webpack.prod.config.js
+++ b/node_modules/@openedx/frontend-build/config/webpack.prod.config.js
@@ -12,6 +12,7 @@ const NewRelicSourceMapPlugin = require('@edx/new-relic-source-map-webpack-plugi
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
+const fs = require('fs');
const PostCssAutoprefixerPlugin = require('autoprefixer');
const PostCssRTLCSS = require('postcss-rtlcss');
const PostCssCustomMediaCSS = require('postcss-custom-media');
@@ -23,6 +24,23 @@ const HtmlWebpackNewRelicPlugin = require('../lib/plugins/html-webpack-new-relic
const commonConfig = require('./webpack.common.config');
const presets = require('../lib/presets');
+/**
+ * This condition confirms whether the configuration for the MFE has switched to a JS-based configuration
+ * as previously implemented in frontend-build and frontend-platform. If the environment variable JS_CONFIG_FILEPATH
+ * exists, then an env.config.js(x) file will be copied from the location referenced by the environment variable to the
+ * root directory. Its env variables can be accessed with getConfig().
+ *
+ * https://github.com/openedx/frontend-build/blob/master/docs/0002-js-environment-config.md
+ * https://github.com/openedx/frontend-platform/blob/master/docs/decisions/0007-javascript-file-configuration.rst
+ */
+
+const envConfigPath = process.env.JS_CONFIG_FILEPATH;
+
+if (envConfigPath) {
+ const envConfigFilename = envConfigPath.slice(envConfigPath.indexOf('env.config'));
+ fs.copyFileSync(envConfigPath, envConfigFilename);
+}
+
// Add process env vars. Currently used only for setting the PUBLIC_PATH.
dotenv.config({
path: path.resolve(process.cwd(), '.env'),

View File

@@ -3,8 +3,8 @@ import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
FormattedMessage, FormattedDate, injectIntl, intlShape,
} from '@edx/frontend-platform/i18n';
import { Alert, Hyperlink } from '@openedx/paragon';
import { Info } from '@openedx/paragon/icons';
import { Alert, Hyperlink } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import messages from './messages';

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n';
import { PageBanner } from '@openedx/paragon';
import { PageBanner } from '@edx/paragon';
const AccessExpirationMasqueradeBanner = ({ payload }) => {
const {

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { Alert, Hyperlink } from '@openedx/paragon';
import { WarningFilled } from '@openedx/paragon/icons';
import { Alert, Hyperlink } from '@edx/paragon';
import { WarningFilled } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform';
import genericMessages from './messages';

View File

@@ -6,8 +6,8 @@ import {
FormattedRelativeTime,
FormattedTime,
} from '@edx/frontend-platform/i18n';
import { Alert } from '@openedx/paragon';
import { Info } from '@openedx/paragon/icons';
import { Alert } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { useModel } from '../../generic/model-store';

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n';
import { PageBanner } from '@openedx/paragon';
import { PageBanner } from '@edx/paragon';
import { useModel } from '../../generic/model-store';

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { Alert, Button } from '@openedx/paragon';
import { Info, WarningFilled } from '@openedx/paragon/icons';
import { Alert, Button } from '@edx/paragon';
import { Info, WarningFilled } from '@edx/paragon/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';

View File

@@ -7,8 +7,8 @@ import {
Button,
Spinner,
Icon,
} from '@openedx/paragon';
import { Check, ArrowForward } from '@openedx/paragon/icons';
} from '@edx/paragon';
import { Check, ArrowForward } from '@edx/paragon/icons';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { sendActivationEmail } from '../../courseware/data';
import messages from './messages';

View File

@@ -2,8 +2,8 @@ import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import { getLoginRedirectUrl } from '@edx/frontend-platform/auth';
import { Alert, Hyperlink } from '@openedx/paragon';
import { WarningFilled } from '@openedx/paragon/icons';
import { Alert, Hyperlink } from '@edx/paragon';
import { WarningFilled } from '@edx/paragon/icons';
import genericMessages from '../../generic/messages';

View File

@@ -16,16 +16,15 @@ export const DECODE_ROUTES = {
],
REDIRECT_HOME: 'home/:courseId',
REDIRECT_SURVEY: 'survey/:courseId',
} as const satisfies Readonly<{ [k: string]: string | readonly string[] }>;
};
export const ROUTES = {
UNSUBSCRIBE: '/goal-unsubscribe/:token',
PREFERENCES_UNSUBSCRIBE: '/preferences-unsubscribe/:userToken/:updatePatch',
REDIRECT: '/redirect/*',
DASHBOARD: 'dashboard',
ENTERPRISE_LEARNER_DASHBOARD: 'enterprise-learner-dashboard',
CONSENT: 'consent',
} as const satisfies Readonly<{ [k: string]: string }>;
};
export const REDIRECT_MODES = {
DASHBOARD_REDIRECT: 'dashboard-redirect',
@@ -33,26 +32,4 @@ export const REDIRECT_MODES = {
CONSENT_REDIRECT: 'consent-redirect',
HOME_REDIRECT: 'home-redirect',
SURVEY_REDIRECT: 'survey-redirect',
} as const satisfies Readonly<{ [k: string]: string }>;
export const VERIFIED_MODES = [
'professional',
'verified',
'no-id-professional',
'credit',
'masters',
'executive-education',
'paid-executive-education',
'paid-bootcamp',
] as const satisfies readonly string[];
export const WIDGETS = {
DISCUSSIONS: 'DISCUSSIONS',
NOTIFICATIONS: 'NOTIFICATIONS',
} as const satisfies Readonly<{ [k: string]: string }>;
export const LOADING = 'loading';
export const LOADED = 'loaded';
export const FAILED = 'failed';
export const DENIED = 'denied';
export type StatusValue = typeof LOADING | typeof LOADED | typeof FAILED | typeof DENIED;
};

View File

@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Tabs, Tab } from '@openedx/paragon';
import { Tabs, Tab } from '@edx/paragon';
import { useParams } from 'react-router';
import CoursewareSearchResults from './CoursewareSearchResults';
@@ -8,10 +8,15 @@ import messages from './messages';
import { useCoursewareSearchParams } from './hooks';
import { useModel } from '../../generic/model-store';
const filterAll = 'all';
const filterTypes = ['text', 'video', 'sequence'];
const filterOther = 'other';
const validFilters = [filterAll, ...filterTypes, filterOther];
const allFilterKey = 'all';
const otherFilterKey = 'other';
const allowedFilterKeys = {
[allFilterKey]: true,
text: true,
video: true,
sequence: true,
[otherFilterKey]: true,
};
export const CoursewareSearchResultsFilter = ({ intl }) => {
const { courseId } = useParams();
@@ -22,38 +27,23 @@ export const CoursewareSearchResultsFilter = ({ intl }) => {
const { results: data = [] } = lastSearch;
// If there's no data, we show an empty result.
if (!data.length) { return <CoursewareSearchResults />; }
const results = useMemo(() => data.reduce((acc, { type, ...rest }) => {
acc[allFilterKey] = [...(acc[allFilterKey] || []), { type: allFilterKey, ...rest }];
if (type === allFilterKey) { return acc; }
const results = useMemo(() => {
// This reducer distributes the data into different groups to make it easy to
// use on the filters.
// All results are added to the "all" key and then to its proper group key as well.
const grouped = data.reduce((acc, { type, ...rest }) => {
const resultType = filterTypes.includes(type) ? type : filterOther;
acc[filterAll].push({ type: resultType, ...rest });
acc[resultType] = [...(acc[resultType] || []), { type: resultType, ...rest }];
return acc;
}, { [filterAll]: [] });
let targetKey = otherFilterKey;
if (allowedFilterKeys[type]) { targetKey = type; }
acc[targetKey] = [...(acc[targetKey] || []), { type: targetKey, ...rest }];
return acc;
}, {}), [data]);
// This is just to format the output object with the expected tab order.
const output = {};
validFilters.forEach(key => { if (grouped[key]) { output[key] = grouped[key]; } });
return output;
}, [lastSearch]);
const tabKeys = Object.keys(results);
// Filter has no use if it has only 2 tabs (The "all" tab and another one with the same items).
if (tabKeys.length < 3) { return <CoursewareSearchResults results={results[filterAll]} />; }
const filters = useMemo(() => tabKeys.map((key) => ({
const filters = useMemo(() => Object.keys(allowedFilterKeys).map((key) => ({
key,
label: intl.formatMessage(messages[`filter:${key}`]),
count: results[key].length,
count: results[key]?.length || 0,
})), [results]);
const activeKey = validFilters.includes(filterKeyword) ? filterKeyword : filterAll;
const activeKey = allowedFilterKeys[filterKeyword] ? filterKeyword : allFilterKey;
return (
<Tabs
@@ -64,7 +54,7 @@ export const CoursewareSearchResultsFilter = ({ intl }) => {
activeKey={activeKey}
onSelect={setFilter}
>
{filters.filter(({ count }) => (count > 0)).map(({ key, label }) => (
{filters.map(({ key, label }) => (
<Tab key={key} eventKey={key} title={label} data-testid={`courseware-search-results-tabs-${key}`}>
<CoursewareSearchResults results={results[key]} />
</Tab>

View File

@@ -19,12 +19,6 @@ jest.mock('../../generic/model-store', () => ({
useModel: jest.fn(),
}));
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));
const decodedCourseId = 'course-v1:edX+DemoX+Demo_Course';
const decodedSequenceId = 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction';
const decodedUnitId = 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_0270f6de40fc';
@@ -58,75 +52,49 @@ function renderComponent(props = {}) {
describe('CoursewareSearchResultsFilter', () => {
beforeAll(initializeMockApp);
beforeEach(() => {
useCoursewareSearchParams.mockReturnValue(coursewareSearch);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('when returning full results', () => {
describe('</CoursewareSearchResultsFilter />', () => {
beforeEach(() => {
useModel.mockReturnValue(searchResultsFactory());
renderComponent();
useCoursewareSearchParams.mockReturnValue(coursewareSearch);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should render without errors', async () => {
await waitFor(() => {
expect(useCoursewareSearchParams).toBeCalled();
});
useModel.mockReturnValue(searchResultsFactory());
expect(screen.queryByTestId('courseware-search-results-tabs')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-all')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-text')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-video')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-sequence')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-other')).toBeInTheDocument();
});
});
describe('when returning only one result type', () => {
beforeEach(async () => {
// Get results for only videos
const data = searchResultsFactory();
const onlyVideos = data.results.filter(({ type }) => type === 'video');
const filteredResults = {
...data,
results: onlyVideos,
};
useModel.mockReturnValue(filteredResults);
await renderComponent();
});
it('should not render', async () => {
await waitFor(() => {
expect(useCoursewareSearchParams).toBeCalled();
expect(screen.queryByTestId('courseware-search-results-tabs-all')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-text')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-video')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-sequence')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-other')).toBeInTheDocument();
});
expect(screen.queryByTestId('courseware-search-results-tabs')).not.toBeInTheDocument();
});
});
describe('when there are not results', () => {
beforeEach(async () => {
useModel.mockReturnValue(searchResultsFactory('blah', {
results: [],
filters: [],
total: 0,
maxScore: null,
ms: 5,
}));
await renderComponent();
});
it('should not render', async () => {
await waitFor(() => {
expect(useCoursewareSearchParams).toBeCalled();
});
describe('when there are not results', () => {
it('should render without errors', async () => {
useModel.mockReturnValue(searchResultsFactory('blah', {
results: [],
filters: [],
total: 0,
maxScore: null,
ms: 5,
}));
expect(screen.queryByTestId('courseware-search-results-tabs')).not.toBeInTheDocument();
await renderComponent();
await waitFor(() => {
expect(screen.queryByTestId('courseware-search-results-tabs-all')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-text')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-video')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-sequence')).toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-results-tabs-other')).toBeInTheDocument();
});
});
});
});
});

View File

@@ -5,10 +5,10 @@ import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Alert, Button, Icon, Spinner,
} from '@openedx/paragon';
} from '@edx/paragon';
import {
Close,
} from '@openedx/paragon/icons';
} from '@edx/paragon/icons';
import { setShowSearch } from '../data/slice';
import { useCoursewareSearchParams, useElementBoundingBox, useLockScroll } from './hooks';
import messages from './messages';
@@ -103,7 +103,7 @@ const CoursewareSearch = ({ intl, ...sectionProps }) => {
</div>
<div className="courseware-search__outer-content">
<div className="courseware-search__content">
<h1 className="h2">{intl.formatMessage(messages.searchModuleTitle)}</h1>
<h1 class="h2">{intl.formatMessage(messages.searchModuleTitle)}</h1>
<CoursewareSearchForm
searchTerm={searchKeyword}
onSubmit={handleSubmit}
@@ -122,16 +122,16 @@ const CoursewareSearch = ({ intl, ...sectionProps }) => {
)}
{status === 'results' ? (
<>
{total > 0 ? (
<div
className="courseware-search__results-summary"
aria-live="polite"
aria-relevant="all"
aria-atomic="true"
data-testid="courseware-search-summary"
>{intl.formatMessage(messages.searchResultsLabel, { total, keyword: lastSearchKeyword })}
</div>
) : null}
<div
className="courseware-search__results-summary"
aria-live="polite"
aria-relevant="all"
aria-atomic="true"
data-testid="courseware-search-summary"
>{total > 0
? intl.formatMessage(messages.searchResultsLabel, { total, keyword: lastSearchKeyword })
: intl.formatMessage(messages.searchResultsNone)}
</div>
<CoursewareSearchResultsFilterContainer />
</>
) : null}

View File

@@ -236,14 +236,14 @@ describe('CoursewareSearch', () => {
expect(screen.queryByTestId('courseware-search-error')).toBeInTheDocument();
});
it('should not show a summary if there are no results', () => {
it('should show "No results found." if results is empty', () => {
mockModels({
searchKeyword: 'test',
total: 0,
});
renderComponent();
expect(screen.queryByTestId('courseware-search-summary')).not.toBeInTheDocument();
expect(screen.queryByTestId('courseware-search-summary').textContent).toBe('No results found.');
});
it('should show a summary for the results', () => {

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { SearchField } from '@openedx/paragon';
import { SearchField } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';

View File

@@ -1,17 +1,16 @@
import React from 'react';
import {
Folder, TextFields, VideoCamera, Article,
} from '@openedx/paragon/icons';
} from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform';
import { Icon } from '@openedx/paragon';
import { Icon } from '@edx/paragon';
import PropTypes from 'prop-types';
import CoursewareSearchEmpty from './CoursewareSearchEmpty';
const iconTypeMapping = {
document: Folder,
text: TextFields,
video: VideoCamera,
sequence: Folder,
other: Article,
};
const defaultIcon = Article;
@@ -35,7 +34,9 @@ const CoursewareSearchResults = ({ results = [] }) => {
}) => {
const key = type.toLowerCase();
const icon = iconTypeMapping[key] || defaultIcon;
const isExternal = !url.startsWith('/');
const linkProps = isExternal ? {
href: url,
target: '_blank',

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
import { ManageSearch } from '@openedx/paragon/icons';
import { Button, Icon } from '@edx/paragon';
import { Search } from '@edx/paragon/icons';
import { useDispatch } from 'react-redux';
import messages from './messages';
import { useCoursewareSearchFeatureFlag, useCoursewareSearchParams } from './hooks';
@@ -25,17 +25,16 @@ const CoursewareSearchToggle = ({
if (!enabled) { return null; }
return (
<div className="courseware-search-toggle">
<div className="courseware-searc-toggle">
<Button
variant="outline-primary"
variant="tertiary"
size="sm"
className="p-1 mt-2 mr-2"
className="p-1 mt-2 mr-2 rounded-lg"
aria-label={intl.formatMessage(messages.searchOpenAction)}
onClick={handleSearchOpenClick}
data-testid="courseware-search-open-button"
iconAfter={ManageSearch}
>
{intl.formatMessage(messages.contentSearchButton)}
<Icon src={Search} />
</Button>
</div>
);

View File

@@ -26,7 +26,7 @@ exports[`CoursewareSearchResults when list of results is provided should match t
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 4H2v16h20V6H12l-2-2z"
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>
@@ -140,7 +140,7 @@ exports[`CoursewareSearchResults when list of results is provided should match t
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 4H2v16h20V6H12l-2-2z"
d="M3 3v18h18V3H3Zm11 14H7v-2h7v2Zm3-4H7v-2h10v2Zm0-4H7V7h10v2Z"
fill="currentColor"
/>
</svg>

View File

@@ -1,29 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`mapSearchResponse when the response is correct should match snapshot 1`] = `
{
"filters": [
{
Object {
"filters": Array [
Object {
"count": 7,
"key": "capa",
"label": "CAPA",
},
{
Object {
"count": 2,
"key": "sequence",
"label": "Sequence",
},
{
Object {
"count": 9,
"key": "text",
"label": "Text",
},
{
Object {
"count": 1,
"key": "unknown",
"label": "Unknown",
},
{
Object {
"count": 2,
"key": "video",
"label": "Video",
@@ -31,11 +31,11 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
],
"maxScore": 3.4545178,
"ms": 5,
"results": [
{
"results": Array [
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction",
"location": [
"location": Array [
"Introduction",
"Demo Course Overview",
],
@@ -44,10 +44,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "sequence",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@5e009378f0b64585baa0a14b155974b9",
"location": [
"location": Array [
"About Exams and Certificates",
"edX Exams",
"Passing a Course",
@@ -57,10 +57,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@5e009378f0b64585baa0a14b155974b9",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@c7e98fd39a6944edb6b286c32e1150ff",
"location": [
"location": Array [
"About Exams and Certificates",
"edX Exams",
"Passing a Course",
@@ -70,10 +70,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "sequence",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@vertical+block@c7e98fd39a6944edb6b286c32e1150ff",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@0d759dee4f9d459c8956136dbde55f02",
"location": [
"location": Array [
"Example Week 1: Getting Started",
"Homework - Question Styles",
"Text input",
@@ -83,10 +83,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@0d759dee4f9d459c8956136dbde55f02",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@c554538a57664fac80783b99d9d6da7c",
"location": [
"location": Array [
"Example Week 1: Getting Started",
"Homework - Question Styles",
"Pointing on a Picture",
@@ -96,10 +96,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@c554538a57664fac80783b99d9d6da7c",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@45d46192272c4f6db6b63586520bbdf4",
"location": [
"location": Array [
"About Exams and Certificates",
"edX Exams",
"Getting Answers",
@@ -109,10 +109,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@45d46192272c4f6db6b63586520bbdf4",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@video+block@0b9e39477cf34507a7a48f74be381fdd",
"location": [
"location": Array [
"Introduction",
"Demo Course Overview",
"Introduction: Video and Sequences",
@@ -122,10 +122,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "video",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@video+block@0b9e39477cf34507a7a48f74be381fdd",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4",
"location": [
"location": Array [
"Example Week 1: Getting Started",
"Homework - Question Styles",
"Multiple Choice Questions",
@@ -135,10 +135,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@75f9562c77bc4858b61f907bb810d974",
"location": [
"location": Array [
"Example Week 1: Getting Started",
"Homework - Question Styles",
"Numerical Input",
@@ -148,10 +148,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@75f9562c77bc4858b61f907bb810d974",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@video+block@636541acbae448d98ab484b028c9a7f6",
"location": [
"location": Array [
"Example Week 1: Getting Started",
"Lesson 1 - Getting Started",
"Video Presentation Styles",
@@ -161,10 +161,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "video",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@video+block@636541acbae448d98ab484b028c9a7f6",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@python_grader",
"location": [
"location": Array [
"Example Week 2: Get Interactive",
"Homework - Labs and Demos",
"Code Grader",
@@ -174,10 +174,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@python_grader",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@9cee77a606ea4c1aa5440e0ea5d0f618",
"location": [
"location": Array [
"Example Week 1: Getting Started",
"Lesson 1 - Getting Started",
"Interactive Questions",
@@ -187,10 +187,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "capa",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@problem+block@9cee77a606ea4c1aa5440e0ea5d0f618",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4",
"location": [
"location": Array [
"Introduction",
"Demo Course Overview",
"Introduction: Video and Sequences",
@@ -200,10 +200,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@html_49b4494da2f7",
"location": [
"location": Array [
"Example Week 3: Be Social",
"Lesson 3 - Be Social",
"Discussion Forums",
@@ -213,10 +213,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@html_49b4494da2f7",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@f4a39219742149f781a1dda6f43a623c",
"location": [
"location": Array [
"About Exams and Certificates",
"edX Exams",
"Overall Grade Performance",
@@ -226,10 +226,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@f4a39219742149f781a1dda6f43a623c",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@87fa6792d79f4862be098e5169e93339",
"location": [
"location": Array [
"Example Week 3: Be Social",
"Lesson 3 - Be Social",
"Homework - Find Your Study Buddy",
@@ -239,10 +239,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@87fa6792d79f4862be098e5169e93339",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@6018785795994726950614ce7d0f38c5",
"location": [
"location": Array [
"Example Week 3: Be Social",
"Homework - Find Your Study Buddy",
"Homework - Find Your Study Buddy",
@@ -252,10 +252,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@6018785795994726950614ce7d0f38c5",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@f9f3a25e7bab46e583fd1fbbd7a2f6a0",
"location": [
"location": Array [
"Example Week 3: Be Social",
"Lesson 3 - Be Social",
"Be Social",
@@ -265,10 +265,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@f9f3a25e7bab46e583fd1fbbd7a2f6a0",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@8293139743f34377817d537b69911530",
"location": [
"location": Array [
"About Exams and Certificates",
"edX Exams",
"EdX Exams",
@@ -278,10 +278,10 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@8293139743f34377817d537b69911530",
},
{
Object {
"contentHits": 0,
"id": "block-v1:edX+DemoX+Demo_Course+type@html+block@9d5104b502f24ee89c3d2f4ce9d347cf",
"location": [
"location": Array [
"Example Week 1: Getting Started",
"Lesson 1 - Getting Started",
"When Are Your Exams? ",
@@ -291,7 +291,7 @@ exports[`mapSearchResponse when the response is correct should match snapshot 1`
"type": "text",
"url": "/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@html+block@9d5104b502f24ee89c3d2f4ce9d347cf",
},
{
Object {
"contentHits": 0,
"id": "random-element-id",
"location": null,

View File

@@ -6,7 +6,7 @@
right: 0;
bottom: 0;
border-top: 1px solid $light-300;
z-index: $zindex-modal; // Bootstrap's z-index layer for Modals.
z-index: 200;
&__close {
position: absolute !important; // For some reason it gets overridden
@@ -51,8 +51,6 @@
&__empty {
color: $gray-500;
padding: 6rem 0;
text-align: center;
}
&__item {

View File

@@ -70,13 +70,12 @@ export function useLockScroll() {
}, []);
}
const initSearchParams = { q: '', f: '' };
export function useCoursewareSearchParams() {
const [searchParams, setSearchParams] = useSearchParams(initSearchParams);
const clearSearchParams = () => setSearchParams(initSearchParams);
const [searchParams, setSearchParams] = useSearchParams();
const clearSearchParams = () => setSearchParams({ q: '', f: '' });
const query = searchParams.get('q');
const filter = searchParams.get('f')?.toLowerCase();
const filter = searchParams.get('f');
const setQuery = (q) => setSearchParams((params) => ({ q, f: params.get('f') }));
const setFilter = (f) => setSearchParams((params) => ({ q: params.get('q'), f }));

View File

@@ -1,13 +1,9 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { useParams, useSearchParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { fetchCoursewareSearchSettings } from '../data/thunks';
import {
useCoursewareSearchFeatureFlag,
useCoursewareSearchParams,
useCoursewareSearchState,
useElementBoundingBox,
useLockScroll,
useCoursewareSearchFeatureFlag, useCoursewareSearchState, useElementBoundingBox, useLockScroll,
} from './hooks';
jest.mock('react-redux');
@@ -188,45 +184,4 @@ describe('CoursewareSearch Hooks', () => {
expect(removeBodyClassSpy).toHaveBeenCalledWith('_search-no-scroll');
});
});
describe('useSearchParams', () => {
const initSearch = { q: '', f: '' };
const q = { value: '' };
const f = { value: '' };
const mockedQuery = { q, f };
const searchParams = { get: (prop) => mockedQuery[prop].value };
const setSearchParams = jest.fn();
beforeEach(() => {
useSearchParams.mockImplementation(() => [searchParams, setSearchParams]);
});
it('should init the search params properly', () => {
const {
query, filter, setQuery, setFilter, clearSearchParams,
} = useCoursewareSearchParams();
expect(useSearchParams).toBeCalledWith(initSearch);
expect(query).toBe('');
expect(filter).toBe('');
setQuery('setQuery');
expect(setSearchParams).toBeCalledWith(expect.any(Function));
setFilter('setFilter');
expect(setSearchParams).toBeCalledWith(expect.any(Function));
clearSearchParams();
expect(setSearchParams).toBeCalledWith(initSearch);
});
it('should return the query and lowercase filter if any', () => {
q.value = '42';
f.value = 'LOWERCASE';
const { query, filter } = useCoursewareSearchParams();
expect(query).toBe('42');
expect(filter).toBe('lowercase');
});
});
});

View File

@@ -2,84 +2,79 @@ import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
searchOpenAction: {
id: 'learn.coursewareSearch.openAction',
id: 'learn.coursewareSerch.openAction',
defaultMessage: 'Search within this course',
description: 'Aria-label for a button that will pop up Courseware Search.',
},
contentSearchButton: {
id: 'learn.coursewareSearch.contentSearchButton',
defaultMessage: 'Content search',
description: 'Text for a button that will pop up Courseware Search.',
},
searchSubmitLabel: {
id: 'learn.coursewareSearch.submitLabel',
id: 'learn.coursewareSerch.submitLabel',
defaultMessage: 'Search',
description: 'Button label that will submit Courseware Search.',
},
searchClearAction: {
id: 'learn.coursewareSearch.clearAction',
id: 'learn.coursewareSerch.clearAction',
defaultMessage: 'Clear search',
description: 'Button label that will the current Courseware Search input.',
},
searchCloseAction: {
id: 'learn.coursewareSearch.closeAction',
id: 'learn.coursewareSerch.closeAction',
defaultMessage: 'Close the search form',
description: 'Aria-label for a button that will close Courseware Search.',
},
searchModuleTitle: {
id: 'learn.coursewareSearch.searchModuleTitle',
id: 'learn.coursewareSerch.searchModuleTitle',
defaultMessage: 'Search this course',
description: 'Title for the Courseware Search module.',
},
searchBarPlaceholderText: {
id: 'learn.coursewareSearch.searchBarPlaceholderText',
id: 'learn.coursewareSerch.searchBarPlaceholderText',
defaultMessage: 'Search',
description: 'Placeholder text for the Courseware Search input control',
},
loading: {
id: 'learn.coursewareSearch.loading',
id: 'learn.coursewareSerch.loading',
defaultMessage: 'Searching...',
description: 'Screen reader text to use on the spinner while the search is performing.',
},
searchResultsNone: {
id: 'learn.coursewareSearch.searchResultsNone',
id: 'learn.coursewareSerch.searchResultsNone',
defaultMessage: 'No results found.',
description: 'Text to show when the Courseware Search found no results matching the criteria.',
},
searchResultsLabel: {
id: 'learn.coursewareSearch.searchResultsLabel',
id: 'learn.coursewareSerch.searchResultsLabel',
defaultMessage: 'Results for "{keyword}":',
description: 'Text to show above the search results response list.',
},
searchResultsError: {
id: 'learn.coursewareSearch.searchResultsError',
id: 'learn.coursewareSerch.searchResultsError',
defaultMessage: 'There was an error on the search process. Please try again in a few minutes. If the problem persists, please contact the support team.',
description: 'Error message to show to the users when there\'s an error with the endpoint or the returned payload format.',
},
// These are translations for labeling the filters
'filter:all': {
id: 'learn.coursewareSearch.filter:all',
id: 'learn.coursewareSerch.filter:all',
defaultMessage: 'All content',
description: 'Label for the search results filter that shows all content (no filter).',
},
'filter:text': {
id: 'learn.coursewareSearch.filter:text',
id: 'learn.coursewareSerch.filter:text',
defaultMessage: 'Text',
description: 'Label for the search results filter that shows results with text content.',
},
'filter:video': {
id: 'learn.coursewareSearch.filter:video',
id: 'learn.coursewareSerch.filter:video',
defaultMessage: 'Video',
description: 'Label for the search results filter that shows results with video content.',
},
'filter:sequence': {
id: 'learn.coursewareSearch.filter:sequence',
id: 'learn.coursewareSerch.filter:sequence',
defaultMessage: 'Section',
description: 'Label for the search results filter that shows results with section content.',
},
'filter:other': {
id: 'learn.coursewareSearch.filter:other',
id: 'learn.coursewareSerch.filter:other',
defaultMessage: 'Other',
description: 'Label for the search results filter that shows results with other content.',
},

View File

@@ -6,7 +6,6 @@ Factory.define('courseHomeMetadata')
.option('host', 'http://localhost:18000')
.attrs({
title: 'Demonstration Course',
is_new_discussion_sidebar_view_enabled: false,
is_self_paced: false,
is_enrolled: false,
is_staff: false,

View File

@@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize, and save metadata 1`] = `
{
"courseHome": {
Object {
"courseHome": Object {
"courseId": "course-v1:edX+DemoX+Demo_Course",
"courseStatus": "loaded",
"proctoringPanelStatus": "loading",
@@ -12,13 +12,9 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"toastBodyText": null,
"toastHeader": "",
},
"courseware": {
"courseware": Object {
"courseId": null,
"courseOutline": {},
"courseOutlineShouldUpdate": false,
"courseOutlineStatus": "loading",
"courseStatus": "loading",
"coursewareOutlineSidebarSettings": {},
"sequenceId": null,
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
@@ -26,12 +22,12 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"learningAssistant": ObjectContaining {
"conversationId": Any<String>,
},
"models": {
"courseHomeMeta": {
"course-v1:edX+DemoX+Demo_Course": {
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"canViewCertificate": true,
"celebrations": null,
"courseAccess": {
"courseAccess": Object {
"additionalContextUserMessage": null,
"developerMessage": null,
"errorCode": null,
@@ -42,40 +38,39 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"id": "course-v1:edX+DemoX+Demo_Course",
"isEnrolled": false,
"isMasquerading": false,
"isNewDiscussionSidebarViewEnabled": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"start": "2013-02-05T05:00:00Z",
"tabs": [
{
"tabs": Array [
Object {
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
},
{
Object {
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/",
},
{
Object {
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki",
},
{
Object {
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress",
},
{
Object {
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor",
},
{
Object {
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/dates",
@@ -84,7 +79,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"title": "Demonstration Course",
"userTimezone": "UTC",
"username": "MockUser",
"verifiedMode": {
"verifiedMode": Object {
"accessExpirationDate": null,
"currency": "USD",
"currencySymbol": "$",
@@ -94,10 +89,10 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
},
},
},
"dates": {
"course-v1:edX+DemoX+Demo_Course": {
"courseDateBlocks": [
{
"dates": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"courseDateBlocks": Array [
Object {
"date": "2020-05-01T17:59:41Z",
"dateType": "course-start-date",
"description": "",
@@ -106,7 +101,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "",
"title": "Course Starts",
},
{
Object {
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-04T02:59:40.942669Z",
@@ -116,7 +111,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"learnerHasAccess": true,
"title": "Multi Badges Completed",
},
{
Object {
"assignmentType": "Homework",
"date": "2020-05-05T02:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -125,7 +120,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"learnerHasAccess": true,
"title": "Multi Badges Past Due",
},
{
Object {
"assignmentType": "Homework",
"date": "2020-05-27T02:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -135,7 +130,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "Both Past Due 1",
},
{
Object {
"assignmentType": "Homework",
"date": "2020-05-27T02:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -145,7 +140,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "Both Past Due 2",
},
{
Object {
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-28T08:59:40.942669Z",
@@ -156,7 +151,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "One Completed/Due 1",
},
{
Object {
"assignmentType": "Homework",
"date": "2020-05-28T08:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -166,7 +161,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "One Completed/Due 2",
},
{
Object {
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-29T08:59:40.942669Z",
@@ -177,7 +172,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "Both Completed 1",
},
{
Object {
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-29T08:59:40.942669Z",
@@ -188,7 +183,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "Both Completed 2",
},
{
Object {
"date": "2020-06-16T17:59:40.942669Z",
"dateType": "verified-upgrade-deadline",
"description": "Don't miss the opportunity to highlight your new knowledge and skills by earning a verified certificate.",
@@ -197,7 +192,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "Upgrade to Verified Certificate",
},
{
Object {
"assignmentType": "Homework",
"date": "2030-08-17T05:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -207,7 +202,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "One Verified 1",
},
{
Object {
"assignmentType": "Homework",
"date": "2030-08-17T05:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -217,7 +212,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "One Verified 2",
},
{
Object {
"assignmentType": "Homework",
"date": "2030-08-17T05:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -227,7 +222,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "ORA Verified 2",
},
{
Object {
"assignmentType": "Homework",
"date": "2030-08-18T05:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -237,7 +232,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "Both Verified 1",
},
{
Object {
"assignmentType": "Homework",
"date": "2030-08-18T05:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -247,7 +242,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "Both Verified 2",
},
{
Object {
"assignmentType": "Homework",
"date": "2030-08-19T05:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -255,7 +250,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"learnerHasAccess": true,
"title": "One Unreleased 1",
},
{
Object {
"assignmentType": "Homework",
"date": "2030-08-19T05:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -265,7 +260,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "https://example.com/",
"title": "One Unreleased 2",
},
{
Object {
"assignmentType": "Homework",
"date": "2030-08-20T05:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -274,7 +269,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"learnerHasAccess": true,
"title": "Both Unreleased 1",
},
{
Object {
"assignmentType": "Homework",
"date": "2030-08-20T05:59:40.942669Z",
"dateType": "assignment-due-date",
@@ -283,7 +278,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"learnerHasAccess": true,
"title": "Both Unreleased 2",
},
{
Object {
"date": "2030-08-23T00:00:00Z",
"dateType": "course-end-date",
"description": "",
@@ -292,7 +287,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"link": "",
"title": "Course Ends",
},
{
Object {
"date": "2030-09-01T00:00:00Z",
"dateType": "verification-deadline-date",
"description": "You must successfully complete verification before this date to qualify for a Verified Certificate.",
@@ -302,7 +297,7 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
"title": "Verification Deadline",
},
],
"datesBannerInfo": {
"datesBannerInfo": Object {
"contentTypeGatingEnabled": false,
"missedDeadlines": false,
"missedGatedContent": false,
@@ -314,75 +309,10 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
},
},
},
"plugins": {},
"recommendations": {
"recommendations": Object {
"recommendationsStatus": "loading",
},
"specialExams": {
"activeAttempt": null,
"allowProctoringOptOut": false,
"apiErrorMsg": "",
"exam": {
"attempt": {
"attempt_code": "",
"attempt_id": null,
"attempt_status": "",
"course_id": "",
"desktop_application_js_url": "",
"exam_display_name": "",
"exam_started_poll_url": "",
"exam_type": "",
"exam_url_path": "",
"external_id": "",
"in_timed_exam": true,
"ping_interval": null,
"taking_as_proctored": true,
"time_remaining_seconds": null,
"use_legacy_attempt_api": true,
},
"backend": "",
"content_id": "",
"course_id": "",
"due_date": null,
"exam_name": "",
"external_id": "",
"hide_after_due": false,
"id": null,
"is_active": true,
"is_practice_exam": false,
"is_proctored": false,
"prerequisite_status": {
"are_prerequisites_satisifed": true,
"declined_prerequisites": [],
"failed_prerequisites": [],
"pending_prerequisites": [],
"satisfied_prerequisites": [],
},
"time_limit_mins": null,
"type": "",
},
"examAccessToken": {
"exam_access_token": "",
"exam_access_token_expiration": "",
},
"isLoading": true,
"proctoringSettings": {
"exam_proctoring_backend": {
"download_url": "",
"instructions": [],
"name": "",
"rules": {},
},
"integration_specific_email": "",
"learner_notification_from_email": "",
"provider_name": "",
"provider_tech_support_email": "",
"provider_tech_support_phone": "",
"provider_tech_support_url": "",
},
"timeIsOver": false,
},
"tours": {
"tours": Object {
"showCoursewareTour": false,
"showExistingUserCourseHomeTour": false,
"showNewUserCourseHomeModal": false,
@@ -393,8 +323,8 @@ exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize
`;
exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normalize, and save metadata 1`] = `
{
"courseHome": {
Object {
"courseHome": Object {
"courseId": "course-v1:edX+DemoX+Demo_Course",
"courseStatus": "loaded",
"proctoringPanelStatus": "loading",
@@ -404,13 +334,9 @@ exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normali
"toastBodyText": null,
"toastHeader": "",
},
"courseware": {
"courseware": Object {
"courseId": null,
"courseOutline": {},
"courseOutlineShouldUpdate": false,
"courseOutlineStatus": "loading",
"courseStatus": "loading",
"coursewareOutlineSidebarSettings": {},
"sequenceId": null,
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
@@ -418,12 +344,12 @@ exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normali
"learningAssistant": ObjectContaining {
"conversationId": Any<String>,
},
"models": {
"courseHomeMeta": {
"course-v1:edX+DemoX+Demo_Course": {
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"canViewCertificate": true,
"celebrations": null,
"courseAccess": {
"courseAccess": Object {
"additionalContextUserMessage": null,
"developerMessage": null,
"errorCode": null,
@@ -434,40 +360,39 @@ exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normali
"id": "course-v1:edX+DemoX+Demo_Course",
"isEnrolled": false,
"isMasquerading": false,
"isNewDiscussionSidebarViewEnabled": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"start": "2013-02-05T05:00:00Z",
"tabs": [
{
"tabs": Array [
Object {
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
},
{
Object {
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/",
},
{
Object {
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki",
},
{
Object {
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress",
},
{
Object {
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor",
},
{
Object {
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/dates",
@@ -476,7 +401,7 @@ exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normali
"title": "Demonstration Course",
"userTimezone": "UTC",
"username": "MockUser",
"verifiedMode": {
"verifiedMode": Object {
"accessExpirationDate": null,
"currency": "USD",
"currencySymbol": "$",
@@ -486,80 +411,77 @@ exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normali
},
},
},
"outline": {
"course-v1:edX+DemoX+Demo_Course": {
"outline": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"accessExpiration": null,
"canShowUpgradeSock": false,
"certData": {
"certData": Object {
"certStatus": null,
"certWebViewUrl": null,
"certificateAvailableDate": null,
},
"courseBlocks": {
"courses": {
"block-v1:edX+DemoX+Demo_Course+type@course+block@bcdabcdabcdabcdabcdabcdabcdabcd3": {
"courseBlocks": Object {
"courses": Object {
"block-v1:edX+DemoX+Demo_Course+type@course+block@bcdabcdabcdabcdabcdabcdabcdabcd3": Object {
"hasScheduledContent": false,
"id": "course-v1:edX+DemoX+Demo_Course",
"sectionIds": [
"sectionIds": Array [
"block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
],
"title": "bcdabcdabcdabcdabcdabcdabcdabcd3",
},
},
"sections": {
"block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2": {
"sections": Object {
"block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2": Object {
"complete": false,
"courseId": "course-v1:edX+DemoX+Demo_Course",
"hideFromTOC": undefined,
"id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
"resumeBlock": false,
"sequenceIds": [
"sequenceIds": Array [
"block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
],
"title": "Title of Section",
},
},
"sequences": {
"block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1": {
"sequences": Object {
"block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1": Object {
"complete": false,
"description": null,
"due": null,
"effortActivities": 2,
"effortTime": 15,
"hideFromTOC": undefined,
"icon": null,
"id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
"navigationDisabled": undefined,
"sectionId": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
"showLink": true,
"title": "Title of Sequence",
},
},
},
"courseGoals": {
"courseGoals": Object {
"daysPerWeek": null,
"goalOptions": [],
"goalOptions": Array [],
"selectedGoal": null,
"subscribedToReminders": null,
"weeklyLearningGoalEnabled": false,
},
"courseTools": [
{
"courseTools": Array [
Object {
"analyticsId": "edx.bookmarks",
"title": "Bookmarks",
"url": "https://example.com/bookmarks",
},
],
"datesBannerInfo": {
"datesBannerInfo": Object {
"contentTypeGatingEnabled": false,
"missedDeadlines": false,
"missedGatedContent": false,
},
"datesWidget": {
"courseDateBlocks": [],
"datesWidget": Object {
"courseDateBlocks": Array [],
},
"enableProctoredExams": undefined,
"enrollAlert": {
"enrollAlert": Object {
"canEnroll": true,
"extraText": "Contact the administrator.",
},
@@ -569,13 +491,13 @@ exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normali
"hasScheduledContent": null,
"id": "course-v1:edX+DemoX+Demo_Course",
"offer": null,
"resumeCourse": {
"resumeCourse": Object {
"hasVisitedCourse": false,
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+Test+Block@12345abcde",
},
"timeOffsetMillis": 0,
"userHasPassingGrade": undefined,
"verifiedMode": {
"verifiedMode": Object {
"accessExpirationDate": "2050-01-01T12:00:00",
"currency": "USD",
"currencySymbol": "$",
@@ -587,75 +509,10 @@ exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normali
},
},
},
"plugins": {},
"recommendations": {
"recommendations": Object {
"recommendationsStatus": "loading",
},
"specialExams": {
"activeAttempt": null,
"allowProctoringOptOut": false,
"apiErrorMsg": "",
"exam": {
"attempt": {
"attempt_code": "",
"attempt_id": null,
"attempt_status": "",
"course_id": "",
"desktop_application_js_url": "",
"exam_display_name": "",
"exam_started_poll_url": "",
"exam_type": "",
"exam_url_path": "",
"external_id": "",
"in_timed_exam": true,
"ping_interval": null,
"taking_as_proctored": true,
"time_remaining_seconds": null,
"use_legacy_attempt_api": true,
},
"backend": "",
"content_id": "",
"course_id": "",
"due_date": null,
"exam_name": "",
"external_id": "",
"hide_after_due": false,
"id": null,
"is_active": true,
"is_practice_exam": false,
"is_proctored": false,
"prerequisite_status": {
"are_prerequisites_satisifed": true,
"declined_prerequisites": [],
"failed_prerequisites": [],
"pending_prerequisites": [],
"satisfied_prerequisites": [],
},
"time_limit_mins": null,
"type": "",
},
"examAccessToken": {
"exam_access_token": "",
"exam_access_token_expiration": "",
},
"isLoading": true,
"proctoringSettings": {
"exam_proctoring_backend": {
"download_url": "",
"instructions": [],
"name": "",
"rules": {},
},
"integration_specific_email": "",
"learner_notification_from_email": "",
"provider_name": "",
"provider_tech_support_email": "",
"provider_tech_support_phone": "",
"provider_tech_support_url": "",
},
"timeIsOver": false,
},
"tours": {
"tours": Object {
"showCoursewareTour": false,
"showExistingUserCourseHomeTour": false,
"showNewUserCourseHomeModal": false,
@@ -666,8 +523,8 @@ exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normali
`;
exports[`Data layer integration tests Test fetchProgressTab Should fetch, normalize, and save metadata 1`] = `
{
"courseHome": {
Object {
"courseHome": Object {
"courseId": "course-v1:edX+DemoX+Demo_Course",
"courseStatus": "loaded",
"proctoringPanelStatus": "loading",
@@ -677,13 +534,9 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
"toastBodyText": null,
"toastHeader": "",
},
"courseware": {
"courseware": Object {
"courseId": null,
"courseOutline": {},
"courseOutlineShouldUpdate": false,
"courseOutlineStatus": "loading",
"courseStatus": "loading",
"coursewareOutlineSidebarSettings": {},
"sequenceId": null,
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
@@ -691,12 +544,12 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
"learningAssistant": ObjectContaining {
"conversationId": Any<String>,
},
"models": {
"courseHomeMeta": {
"course-v1:edX+DemoX+Demo_Course": {
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"canViewCertificate": true,
"celebrations": null,
"courseAccess": {
"courseAccess": Object {
"additionalContextUserMessage": null,
"developerMessage": null,
"errorCode": null,
@@ -707,40 +560,39 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
"id": "course-v1:edX+DemoX+Demo_Course",
"isEnrolled": false,
"isMasquerading": false,
"isNewDiscussionSidebarViewEnabled": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"start": "2013-02-05T05:00:00Z",
"tabs": [
{
"tabs": Array [
Object {
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
},
{
Object {
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/",
},
{
Object {
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki",
},
{
Object {
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress",
},
{
Object {
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor",
},
{
Object {
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/dates",
@@ -749,7 +601,7 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
"title": "Demonstration Course",
"userTimezone": "UTC",
"username": "MockUser",
"verifiedMode": {
"verifiedMode": Object {
"accessExpirationDate": null,
"currency": "USD",
"currencySymbol": "$",
@@ -759,16 +611,16 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
},
},
},
"progress": {
"course-v1:edX+DemoX+Demo_Course": {
"progress": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"accessExpiration": null,
"certificateData": {},
"completionSummary": {
"certificateData": Object {},
"completionSummary": Object {
"completeCount": 1,
"incompleteCount": 1,
"lockedCount": 0,
},
"courseGrade": {
"courseGrade": Object {
"isPassing": true,
"letterGrade": "pass",
"percent": 1,
@@ -779,10 +631,10 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
"enrollmentMode": "audit",
"gradesFeatureIsFullyLocked": false,
"gradesFeatureIsPartiallyLocked": false,
"gradingPolicy": {
"assignmentPolicies": [
{
"averageGrade": "1.0000",
"gradingPolicy": Object {
"assignmentPolicies": Array [
Object {
"averageGrade": "1.00",
"numDroppable": 1,
"shortLabel": "HW",
"type": "Homework",
@@ -790,17 +642,17 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
"weightedGrade": 1,
},
],
"gradeRange": {
"gradeRange": Object {
"pass": 0.75,
},
},
"hasScheduledContent": false,
"id": "course-v1:edX+DemoX+Demo_Course",
"sectionScores": [
{
"sectionScores": Array [
Object {
"displayName": "First section",
"subsections": [
{
"subsections": Array [
Object {
"assignmentType": "Homework",
"blockKey": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345",
"displayName": "First subsection",
@@ -809,16 +661,16 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
"numPointsEarned": 0,
"numPointsPossible": 3,
"percentGraded": 0,
"problemScores": [
{
"problemScores": Array [
Object {
"earned": 0,
"possible": 1,
},
{
Object {
"earned": 0,
"possible": 1,
},
{
Object {
"earned": 0,
"possible": 1,
},
@@ -829,18 +681,18 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
},
],
},
{
Object {
"displayName": "Second section",
"subsections": [
{
"subsections": Array [
Object {
"assignmentType": "Homework",
"displayName": "Second subsection",
"hasGradedAssignment": true,
"numPointsEarned": 1,
"numPointsPossible": 1,
"percentGraded": 1,
"problemScores": [
{
"problemScores": Array [
Object {
"earned": 1,
"possible": 1,
},
@@ -854,7 +706,7 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
],
"studioUrl": "http://studio.edx.org/settings/grading/course-v1:edX+Test+run",
"userHasPassingGrade": false,
"verificationData": {
"verificationData": Object {
"link": null,
"status": "none",
"statusDate": null,
@@ -863,75 +715,10 @@ exports[`Data layer integration tests Test fetchProgressTab Should fetch, normal
},
},
},
"plugins": {},
"recommendations": {
"recommendations": Object {
"recommendationsStatus": "loading",
},
"specialExams": {
"activeAttempt": null,
"allowProctoringOptOut": false,
"apiErrorMsg": "",
"exam": {
"attempt": {
"attempt_code": "",
"attempt_id": null,
"attempt_status": "",
"course_id": "",
"desktop_application_js_url": "",
"exam_display_name": "",
"exam_started_poll_url": "",
"exam_type": "",
"exam_url_path": "",
"external_id": "",
"in_timed_exam": true,
"ping_interval": null,
"taking_as_proctored": true,
"time_remaining_seconds": null,
"use_legacy_attempt_api": true,
},
"backend": "",
"content_id": "",
"course_id": "",
"due_date": null,
"exam_name": "",
"external_id": "",
"hide_after_due": false,
"id": null,
"is_active": true,
"is_practice_exam": false,
"is_proctored": false,
"prerequisite_status": {
"are_prerequisites_satisifed": true,
"declined_prerequisites": [],
"failed_prerequisites": [],
"pending_prerequisites": [],
"satisfied_prerequisites": [],
},
"time_limit_mins": null,
"type": "",
},
"examAccessToken": {
"exam_access_token": "",
"exam_access_token_expiration": "",
},
"isLoading": true,
"proctoringSettings": {
"exam_proctoring_backend": {
"download_url": "",
"instructions": [],
"name": "",
"rules": {},
},
"integration_specific_email": "",
"learner_notification_from_email": "",
"provider_name": "",
"provider_tech_support_email": "",
"provider_tech_support_phone": "",
"provider_tech_support_url": "",
},
"timeIsOver": false,
},
"tours": {
"tours": Object {
"showCoursewareTour": false,
"showExistingUserCourseHomeTour": false,
"showNewUserCourseHomeModal": false,

View File

@@ -18,7 +18,7 @@ const calculateAssignmentTypeGrades = (points, assignmentWeight, numDroppable) =
// Calculate the average grade for the assignment and round it. This rounding is not ideal and does not accurately
// reflect what a learner's grade would be, however, we must have parity with the current grading behavior that
// exists in edx-platform.
averageGrade = (points.reduce((a, b) => a + b, 0) / points.length).toFixed(4);
averageGrade = (points.reduce((a, b) => a + b, 0) / points.length).toFixed(2);
weightedGrade = averageGrade * assignmentWeight;
}
return { averageGrade, weightedGrade };
@@ -136,7 +136,6 @@ export function normalizeOutlineBlocks(courseId, blocks) {
title: block.display_name,
resumeBlock: block.resume_block,
sequenceIds: block.children || [],
hideFromTOC: block.hide_from_toc,
};
break;
@@ -153,8 +152,6 @@ export function normalizeOutlineBlocks(courseId, blocks) {
// link in the outline (even though we ignore the given url and use an internal <Link> to ourselves).
showLink: !!block.lms_web_url,
title: block.display_name,
hideFromTOC: block.hide_from_toc,
navigationDisabled: block.navigation_disabled,
};
break;

View File

@@ -1,12 +1,10 @@
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';
import {
LOADING,
LOADED,
FAILED,
DENIED,
} from '@src/constants';
export const LOADING = 'loading';
export const LOADED = 'loaded';
export const FAILED = 'failed';
export const DENIED = 'denied';
const slice = createSlice({
name: 'course-home',

View File

@@ -8,7 +8,7 @@ import {
injectIntl,
intlShape,
} from '@edx/frontend-platform/i18n';
import { Tooltip, OverlayTrigger } from '@openedx/paragon';
import { Tooltip, OverlayTrigger } from '@edx/paragon';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

View File

@@ -2,7 +2,7 @@ import React from 'react';
import classNames from 'classnames';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faLock } from '@fortawesome/free-solid-svg-icons';
import { Badge } from '@openedx/paragon';
import { Badge } from '@edx/paragon';
import messages from '../messages';
import { daycmp, isLearnerAssignment } from '../utils';
@@ -38,21 +38,21 @@ function getBadgeListAndColor(date, intl, item, items) {
message: messages.today,
shownForDay: isToday,
bg: 'bg-warning-300',
className: 'text-dark',
className: 'text-black',
},
{
message: messages.completed,
shownForDay: assignments.length && assignments.every(isComplete),
shownForItem: x => isLearnerAssignment(x) && isComplete(x),
bg: 'bg-light-500',
className: 'text-dark',
className: 'text-black',
},
{
message: messages.pastDue,
shownForDay: assignments.length && assignments.every(isPastDue),
shownForItem: x => isLearnerAssignment(x) && isPastDue(x),
bg: 'bg-dark-200',
className: 'text-dark',
className: 'text-white',
},
{
message: messages.dueNext,

View File

@@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import HeaderSlot from '../../plugin-slots/HeaderSlot';
import { LearningHeader as Header } from '@edx/frontend-component-header';
import PageLoading from '../../generic/PageLoading';
import { unsubscribeFromCourseGoal } from '../data/api';
@@ -38,7 +38,7 @@ const GoalUnsubscribe = ({ intl }) => {
return (
<>
<HeaderSlot showUserDropdown={false} />
<Header showUserDropdown={false} />
<main id="main-content" className="container my-5 text-center">
{isLoading && (
<PageLoading srMessage={`${intl.formatMessage(messages.loading)}`} />

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button, Hyperlink } from '@openedx/paragon';
import { Button, Hyperlink } from '@edx/paragon';
import messages from './messages';
import { ReactComponent as UnsubscribeIcon } from './unsubscribe.svg';

View File

@@ -9,9 +9,8 @@ const LmsHtmlFragment = ({
title,
...rest
}) => {
const direction = document.documentElement?.getAttribute('dir') || 'ltr';
const wholePage = `
<html dir="${direction}">
<html>
<head>
<base href="${getConfig().LMS_BASE_URL}" target="_parent">
<link rel="stylesheet" href="/static/${getConfig().LEGACY_THEME_NAME ? `${getConfig().LEGACY_THEME_NAME}/` : ''}css/bootstrap/lms-main.css">
@@ -30,7 +29,7 @@ const LmsHtmlFragment = ({
const iframe = useRef(null);
function resetIframeHeight() {
if (iframe?.current?.contentWindow?.document?.body) {
iframe.current.height = iframe.current.contentWindow.document.body.parentNode.scrollHeight;
iframe.current.height = iframe.current.contentWindow.document.body.scrollHeight;
}
}

View File

@@ -4,8 +4,7 @@ import { useSelector } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { Button } from '@edx/paragon';
import { AlertList } from '../../generic/user-messages';
import CourseDates from './widgets/CourseDates';
@@ -195,27 +194,19 @@ const OutlineTab = ({ intl }) => {
/>
)}
<CourseTools />
<PluginSlot
id="outline_tab_notifications_slot"
pluginProps={{
courseId,
model: 'outline',
}}
>
<UpgradeNotification
offer={offer}
verifiedMode={verifiedMode}
accessExpiration={accessExpiration}
contentTypeGatingEnabled={datesBannerInfo.contentTypeGatingEnabled}
marketingUrl={marketingUrl}
upsellPageName="course_home"
userTimezone={userTimezone}
shouldDisplayBorder
timeOffsetMillis={timeOffsetMillis}
courseId={courseId}
org={org}
/>
</PluginSlot>
<UpgradeNotification
offer={offer}
verifiedMode={verifiedMode}
accessExpiration={accessExpiration}
contentTypeGatingEnabled={datesBannerInfo.contentTypeGatingEnabled}
marketingUrl={marketingUrl}
upsellPageName="course_home"
userTimezone={userTimezone}
shouldDisplayBorder
timeOffsetMillis={timeOffsetMillis}
courseId={courseId}
org={org}
/>
<CourseDates />
<CourseHandouts />
</div>

View File

@@ -132,16 +132,6 @@ describe('Outline Tab', () => {
expect(expandedSectionNode).toHaveAttribute('aria-expanded', 'true');
});
it('includes outline_tab_notifications_slot', async () => {
const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true });
setTabData({
course_blocks: { blocks: courseBlocks.blocks },
});
await fetchAndRender();
expect(screen.getByTestId('outline_tab_notifications_slot')).toBeInTheDocument();
});
it('handles expand/collapse all button click', async () => {
await fetchAndRender();
// Button renders as "Expand All"
@@ -292,22 +282,6 @@ describe('Outline Tab', () => {
});
});
it('ignores comments and misformatted HTML', async () => {
setTabData({
welcome_message_html: '<p class="additional-spaces-in-tag" >'
+ '<!-- Even if the welcome_message_html length is above the limit because of comments, we hope it will not be shortened. -->'
+ '<!-- Even if the welcome_message_html length is above the limit because of comments, we hope it will not be shortened. -->'
+ 'Test welcome message that happens to be longer than one hundred words because of comments but displayed content is less.'
+ 'It should not be shortened.'
+ '<!-- Even if the welcome_message_html length is above the limit because of comments, we hope it will not be shortened. -->'
+ '<!-- Even if the welcome_message_html length is above the limit because of comments, we hope it will not be shortened. -->'
+ '</p>',
});
await fetchAndRender();
const showMoreButton = screen.queryByRole('button', { name: 'Show More' });
expect(showMoreButton).not.toBeInTheDocument();
});
it('does not display if no update available', async () => {
setTabData({ welcome_message_html: null });
await fetchAndRender();
@@ -1295,97 +1269,5 @@ describe('Outline Tab', () => {
await waitFor(() => expect(axiosMock.history.post).toHaveLength(1));
expect(axiosMock.history.post[0].url).toEqual(resendEmailUrl);
});
it('section should show hidden from toc message when hide_from_toc is true', async () => {
const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true });
const courseBlocksIds = Object.keys(courseBlocks.blocks);
const newCourseBlocks = courseBlocksIds.reduce((blocks, blockId) => ({
...blocks,
[blockId]: {
...courseBlocks.blocks[blockId],
hide_from_toc: true,
},
}), {});
setTabData({
course_blocks: { blocks: newCourseBlocks },
});
await fetchAndRender();
const iconHiddenFromTocSectionNode = screen.getByTestId('hide-from-toc-section-icon');
const textHiddenFromTocSectionNode = screen.getByTestId('hide-from-toc-section-text');
expect(iconHiddenFromTocSectionNode).toBeInTheDocument();
expect(textHiddenFromTocSectionNode).toBeInTheDocument();
expect(textHiddenFromTocSectionNode.textContent).toBe('Hidden in Course Outline, accessible via link');
});
it('section should not show hidden from toc message when hide_from_toc is false', async () => {
const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true });
const courseBlocksIds = Object.keys(courseBlocks.blocks);
const newCourseBlocks = courseBlocksIds.reduce((blocks, blockId) => ({
...blocks,
[blockId]: {
...courseBlocks.blocks[blockId],
hide_from_toc: false,
},
}), {});
setTabData({
course_blocks: { blocks: newCourseBlocks },
});
await fetchAndRender();
const iconHiddenFromTocSectionNode = screen.queryByTestId('hide-from-toc-section-icon');
const textHiddenFromTocSectionNode = screen.queryByTestId('hide-from-toc-section-text');
expect(iconHiddenFromTocSectionNode).not.toBeInTheDocument();
expect(textHiddenFromTocSectionNode).not.toBeInTheDocument();
});
it('sequence link should show hidden from toc message when hide_from_toc is true', async () => {
const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true });
const courseBlocksIds = Object.keys(courseBlocks.blocks);
const newCourseBlocks = courseBlocksIds.reduce((blocks, blockId) => ({
...blocks,
[blockId]: {
...courseBlocks.blocks[blockId],
hide_from_toc: true,
},
}), {});
setTabData({
course_blocks: { blocks: newCourseBlocks },
});
await fetchAndRender();
const iconHiddenFromTocSequenceLinkNode = screen.getByTestId('hide-from-toc-sequence-link-icon');
const textHiddenFromTocSequenceLink = screen.getByTestId('hide-from-toc-sequence-link-text');
expect(iconHiddenFromTocSequenceLinkNode).toBeInTheDocument();
expect(textHiddenFromTocSequenceLink).toBeInTheDocument();
expect(textHiddenFromTocSequenceLink.textContent).toBe('Subsections are not navigable between each other, they can only be accessed through their link.');
});
it('sequence link not show hidden from toc message when hide_from_toc is false', async () => {
const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true });
const courseBlocksIds = Object.keys(courseBlocks.blocks);
const newCourseBlocks = courseBlocksIds.reduce((blocks, blockId) => ({
...blocks,
[blockId]: {
...courseBlocks.blocks[blockId],
hide_from_toc: false,
},
}), {});
setTabData({
course_blocks: { blocks: newCourseBlocks },
});
await fetchAndRender();
const iconHiddenFromTocSequenceLink = screen.queryByTestId('hide-from-toc-sequence-link-icon');
const textHiddenFromTocSequenceLink = screen.queryByTestId('hide-from-toc-sequence-link-text');
expect(iconHiddenFromTocSequenceLink).not.toBeInTheDocument();
expect(textHiddenFromTocSequenceLink).not.toBeInTheDocument();
});
});
});

View File

@@ -1,12 +1,11 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Collapsible, IconButton, Icon } from '@openedx/paragon';
import { Collapsible, IconButton } from '@edx/paragon';
import { faCheckCircle as fasCheckCircle, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
import { faCheckCircle as farCheckCircle } from '@fortawesome/free-regular-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { DisabledVisible } from '@openedx/paragon/icons';
import SequenceLink from './SequenceLink';
import { useModel } from '../../generic/model-store';
@@ -24,7 +23,6 @@ const Section = ({
complete,
sequenceIds,
title,
hideFromTOC,
} = section;
const {
courseBlocks: {
@@ -44,7 +42,7 @@ const Section = ({
}, []);
const sectionTitle = (
<div className="d-flex row w-100 m-0">
<div className="row w-100 m-0">
<div className="col-auto p-0">
{complete ? (
<FontAwesomeIcon
@@ -64,24 +62,12 @@ const Section = ({
/>
)}
</div>
<div className="col-7 ml-3 p-0 font-weight-bold text-dark-500">
<span className="align-middle col-6">{title}</span>
<div className="col-10 ml-3 p-0 font-weight-bold text-dark-500">
<span className="align-middle">{title}</span>
<span className="sr-only">
, {intl.formatMessage(complete ? messages.completedSection : messages.incompleteSection)}
</span>
</div>
{hideFromTOC && (
<div className="row">
{hideFromTOC && (
<span className="small d-flex align-content-end">
<Icon className="mr-2" src={DisabledVisible} data-testid="hide-from-toc-section-icon" />
<span data-testid="hide-from-toc-section-text">
{intl.formatMessage(messages.hiddenSection)}
</span>
</span>
)}
</div>
)}
</div>
);

View File

@@ -1,3 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
@@ -11,8 +12,6 @@ import { faCheckCircle as fasCheckCircle } from '@fortawesome/free-solid-svg-ico
import { faCheckCircle as farCheckCircle } from '@fortawesome/free-regular-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Icon } from '@openedx/paragon';
import { Block } from '@openedx/paragon/icons';
import EffortEstimate from '../../shared/effort-estimate';
import { useModel } from '../../generic/model-store';
import messages from './messages';
@@ -30,7 +29,6 @@ const SequenceLink = ({
due,
showLink,
title,
hideFromTOC,
} = sequence;
const {
userTimezone,
@@ -95,7 +93,7 @@ const SequenceLink = ({
icon={fasCheckCircle}
fixedWidth
className="float-left text-success mt-1"
aria-hidden={complete}
aria-hidden="true"
title={intl.formatMessage(messages.completedAssignment)}
/>
) : (
@@ -103,7 +101,7 @@ const SequenceLink = ({
icon={farCheckCircle}
fixedWidth
className="float-left text-gray-400 mt-1"
aria-hidden={complete}
aria-hidden="true"
title={intl.formatMessage(messages.incompleteAssignment)}
/>
)}
@@ -116,16 +114,6 @@ const SequenceLink = ({
<EffortEstimate className="ml-3 align-middle" block={sequence} />
</div>
</div>
{hideFromTOC && (
<div className="row w-100 my-2 mx-4 pl-3">
<span className="small d-flex">
<Icon className="mr-2" src={Block} data-testid="hide-from-toc-sequence-link-icon" />
<span data-testid="hide-from-toc-sequence-link-text">
{intl.formatMessage(messages.hiddenSequenceLink)}
</span>
</span>
</div>
)}
<div className="row w-100 m-0 ml-3 pl-3">
<small className="text-body pl-2">
{due ? dueDateMessage : noDueDateMessage}

View File

@@ -6,7 +6,7 @@ import {
injectIntl,
intlShape,
} from '@edx/frontend-platform/i18n';
import { Alert, Button } from '@openedx/paragon';
import { Alert, Button } from '@edx/paragon';
import { useDispatch } from 'react-redux';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

View File

@@ -6,8 +6,8 @@ import {
FormattedRelativeTime,
FormattedTime,
} from '@edx/frontend-platform/i18n';
import { Alert } from '@openedx/paragon';
import { Info } from '@openedx/paragon/icons';
import { Alert } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
const DAY_SEC = 24 * 60 * 60; // in seconds
const DAY_MS = DAY_SEC * 1000; // in ms

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import { getLoginRedirectUrl } from '@edx/frontend-platform/auth';
import { Alert, Button, Hyperlink } from '@openedx/paragon';
import { Alert, Button, Hyperlink } from '@edx/paragon';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';

View File

@@ -1,5 +1,5 @@
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Alert, Button } from '@openedx/paragon';
import { Alert, Button } from '@edx/paragon';
import React from 'react';
import PropTypes from 'prop-types';

View File

@@ -36,16 +36,6 @@ const messages = defineMessages({
defaultMessage: 'Completed section',
description: 'Text used to describe the green checkmark icon in front of a section title',
},
hiddenSection: {
id: 'learning.outline.hiddenSection',
defaultMessage: 'Hidden in Course Outline, accessible via link',
description: 'Label for hidden section in course outline',
},
hiddenSequenceLink: {
id: 'learning.outline.hiddenSequenceLink',
defaultMessage: 'Subsections are not navigable between each other, they can only be accessed through their link.',
description: 'Label for hidden sequence in course outline',
},
dates: {
id: 'learning.outline.dates',
defaultMessage: 'Important dates',

View File

@@ -1,5 +1,5 @@
@import "~@edx/brand/paragon/variables";
@import "~@openedx/paragon/scss/core/core";
@import "~@edx/paragon/scss/core/core";
@import "~@edx/brand/paragon/overrides";
.flag-button {

View File

@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux';
import camelCase from 'lodash.camelcase';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
import { Button } from '@edx/paragon';
import messages from '../messages';
import { getProctoringInfoData } from '../../data/api';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Button, Card } from '@openedx/paragon';
import { Button, Card } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';

View File

@@ -2,12 +2,12 @@ import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import PropTypes from 'prop-types';
import { Form, Card, Icon } from '@openedx/paragon';
import { Form, Card, Icon } from '@edx/paragon';
import { history } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Email } from '@openedx/paragon/icons';
import { Email } from '@edx/paragon/icons';
import { useSelector } from 'react-redux';
import messages from '../messages';
import LearningGoalButton from './LearningGoalButton';

View File

@@ -1,8 +1,8 @@
import React, { useState, useMemo } from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert, Button, TransitionReplace } from '@openedx/paragon';
import { Alert, Button, TransitionReplace } from '@edx/paragon';
import truncate from 'truncate-html';
import { useDispatch } from 'react-redux';
@@ -18,22 +18,8 @@ const WelcomeMessage = ({ courseId, intl }) => {
const [display, setDisplay] = useState(true);
// welcomeMessageHtml can contain comments or malformatted HTML which can impact the length that determines
// messageCanBeShortened. We clean it by calling truncate with a length of welcomeMessageHtml.length which
// will not result in a truncation but a formatting into 'truncate-html' canonical format.
const cleanedWelcomeMessageHtml = useMemo(
() => truncate(welcomeMessageHtml, welcomeMessageHtml.length, { keepWhitespaces: true }),
[welcomeMessageHtml],
);
const shortWelcomeMessageHtml = useMemo(
() => truncate(cleanedWelcomeMessageHtml, 100, { byWords: true, keepWhitespaces: true }),
[cleanedWelcomeMessageHtml],
);
const messageCanBeShortened = useMemo(
() => (shortWelcomeMessageHtml.length < cleanedWelcomeMessageHtml.length),
[cleanedWelcomeMessageHtml, shortWelcomeMessageHtml],
);
const shortWelcomeMessageHtml = truncate(welcomeMessageHtml, 100, { byWords: true, keepWhitespaces: true });
const messageCanBeShortened = shortWelcomeMessageHtml.length < welcomeMessageHtml.length;
const [showShortMessage, setShowShortMessage] = useState(messageCanBeShortened);
const dispatch = useDispatch();
@@ -77,7 +63,7 @@ const WelcomeMessage = ({ courseId, intl }) => {
className="inline-link"
data-testid="long-welcome-message-iframe"
key="full-html"
html={cleanedWelcomeMessageHtml}
html={welcomeMessageHtml}
title={intl.formatMessage(messages.welcomeMessage)}
/>
)}

View File

@@ -3,7 +3,7 @@ import { useSelector } from 'react-redux';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
import { Button } from '@edx/paragon';
import { useModel } from '../../generic/model-store';

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { breakpoints, useWindowSize } from '@openedx/paragon';
import { breakpoints, useWindowSize } from '@edx/paragon';
import CertificateStatus from './certificate-status/CertificateStatus';
import CourseCompletion from './course-completion/CourseCompletion';

View File

@@ -3,7 +3,7 @@ import { Factory } from 'rosie';
import { getConfig } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { breakpoints } from '@openedx/paragon';
import { breakpoints } from '@edx/paragon';
import MockAdapter from 'axios-mock-adapter';
import {

View File

@@ -6,7 +6,7 @@ import {
FormattedDate, FormattedMessage, injectIntl, intlShape,
} from '@edx/frontend-platform/i18n';
import { Button, Card } from '@openedx/paragon';
import { Button, Card } from '@edx/paragon';
import { getConfig } from '@edx/frontend-platform';
import { useModel } from '../../../generic/model-store';
import { COURSE_EXIT_MODES, getCourseExitMode } from '../../../courseware/course/course-exit/utils';
@@ -19,10 +19,6 @@ const CertificateStatus = ({ intl }) => {
courseId,
} = useSelector(state => state.courseHome);
const {
entranceExamData,
} = useModel('coursewareMeta', courseId);
const {
isEnrolled,
org,
@@ -46,8 +42,6 @@ const CertificateStatus = ({ intl }) => {
certificateAvailableDate,
} = certificateData || {};
const entranceExamPassed = entranceExamData?.entranceExamPassed ?? null;
const mode = getCourseExitMode(
certificateData,
hasScheduledContent,
@@ -55,7 +49,6 @@ const CertificateStatus = ({ intl }) => {
userHasPassingGrade,
null, // CourseExitPageIsActive
canViewCertificate,
entranceExamPassed,
);
const eventProperties = {
@@ -161,7 +154,7 @@ const CertificateStatus = ({ intl }) => {
certAvailabilityDate = <FormattedDate value={certificateAvailableDate} day="numeric" month="long" year="numeric" />;
body = (
<FormattedMessage
id="progress.certificateStatus.notAvailable.endDate"
id="courseCelebration.certificateBody.notAvailable.endDate"
defaultMessage="This course ends on {endDate}. Final grades and any earned certificates are
scheduled to be available after {certAvailabilityDate}."
description="This shown for leaner when they are eligible for certifcate but it't not available yet, it could because leaners just finished the course quickly!"

View File

@@ -56,6 +56,11 @@ const messages = defineMessages({
defaultMessage: 'Your certificate is available!',
description: 'Header text when the certifcate is available',
},
downloadableBody: {
id: 'progress.certificateStatus.downloadableBody',
defaultMessage: 'Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.',
description: 'Recommending an action for learner when course certificate is available',
},
viewableButton: {
id: 'progress.certificateStatus.viewableButton',
defaultMessage: 'View my certificate',

View File

@@ -2,7 +2,7 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import { OverlayTrigger, Popover } from '@edx/paragon';
import messages from './messages';
@@ -33,7 +33,7 @@ const CompleteDonutSegment = ({ completePercentage, intl, lockedPercentage }) =>
show={showCompletePopover}
placement="top"
overlay={(
<Popover id="complete-content-tooltip-popover" aria-hidden="true">
<Popover aria-hidden="true">
<Popover.Content>
{intl.formatMessage(messages.completeContentTooltip)}
</Popover.Content>

View File

@@ -2,7 +2,7 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import { OverlayTrigger, Popover } from '@edx/paragon';
import messages from './messages';
@@ -37,7 +37,7 @@ const IncompleteDonutSegment = ({ incompletePercentage, intl }) => {
show={showIncompletePopover}
placement="top"
overlay={(
<Popover id="incomplete-tooltip-popover" aria-hidden="true">
<Popover aria-hidden="true">
<Popover.Content>
{intl.formatMessage(messages.incompleteContentTooltip)}
</Popover.Content>

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import { OverlayTrigger, Popover } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
@@ -36,7 +36,7 @@ const LockedDonutSegment = ({ intl, lockedPercentage }) => {
show={showLockedPopover}
placement="top"
overlay={(
<Popover id="locked-tooltip-popover" aria-hidden="true">
<Popover aria-hidden="true">
<Popover.Content>
{intl.formatMessage(messages.lockedContentTooltip)}
</Popover.Content>

View File

@@ -2,8 +2,8 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { CheckCircle, WarningFilled, WatchFilled } from '@openedx/paragon/icons';
import { Hyperlink, Icon } from '@openedx/paragon';
import { CheckCircle, WarningFilled, WatchFilled } from '@edx/paragon/icons';
import { Hyperlink, Icon } from '@edx/paragon';
import { useModel } from '../../../generic/model-store';
import { DashboardLink } from '../../../shared/links';

View File

@@ -3,8 +3,8 @@ import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { CheckCircle, WarningFilled } from '@openedx/paragon/icons';
import { breakpoints, Icon, useWindowSize } from '@openedx/paragon';
import { CheckCircle, WarningFilled } from '@edx/paragon/icons';
import { breakpoints, Icon, useWindowSize } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';
import GradeRangeTooltip from './GradeRangeTooltip';

View File

@@ -4,8 +4,8 @@ import { useSelector } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Locked } from '@openedx/paragon/icons';
import { Button, Icon } from '@openedx/paragon';
import { Locked } from '@edx/paragon/icons';
import { Button, Icon } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';
import messages from '../messages';

View File

@@ -5,7 +5,7 @@ import { useSelector } from 'react-redux';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import { OverlayTrigger, Popover } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';

View File

@@ -36,17 +36,17 @@
}
#passing-grade-tooltip {
background: $success-500;
.arrow::after {
border-top-color: $success-500;
}
background: $success-500;
}
#non-passing-grade-tooltip {
background: $accent-b;
.arrow::after {
border-top-color: $accent-b;
}
background: $accent-b;
}

View File

@@ -3,10 +3,10 @@ import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { InfoOutline } from '@openedx/paragon/icons';
import { InfoOutline } from '@edx/paragon/icons';
import {
Icon, IconButton, OverlayTrigger, Popover,
} from '@openedx/paragon';
} from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';
import messages from '../messages';

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import { OverlayTrigger, Popover } from '@edx/paragon';
import messages from '../messages';

View File

@@ -4,8 +4,8 @@ import { useSelector } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Blocked } from '@openedx/paragon/icons';
import { Icon, Hyperlink } from '@openedx/paragon';
import { Blocked } from '@edx/paragon/icons';
import { Icon, Hyperlink } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';
import DetailedGradesTable from './DetailedGradesTable';

View File

@@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { DataTable } from '@openedx/paragon';
import { DataTable } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';
import messages from '../messages';

View File

@@ -15,9 +15,8 @@ const ProblemScoreDrawer = ({ intl, problemScores, subsection }) => {
<span id="problem-score-label" className="col-auto p-0">{intl.formatMessage(messages.problemScoreLabel)}</span>
<div className={classNames('col', 'p-0', { 'greyed-out': !subsection.learnerHasAccess })}>
<ul className="list-unstyled row w-100 m-0" aria-labelledby="problem-score-label">
{problemScores.map((problemScore, i) => (
// eslint-disable-next-line react/no-array-index-key
<li key={i} className="ml-3">{problemScore.earned}{isLocaleRtl ? '\\' : '/'}{problemScore.possible}</li>
{problemScores.map(problemScore => (
<li className="ml-3">{problemScore.earned}{isLocaleRtl ? '\\' : '/'}{problemScore.possible}</li>
))}
</ul>
</div>

View File

@@ -5,10 +5,10 @@ import PropTypes from 'prop-types';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Collapsible, Icon, Row } from '@openedx/paragon';
import { Collapsible, Icon, Row } from '@edx/paragon';
import {
ArrowDropDown, ArrowDropUp, Blocked, Info,
} from '@openedx/paragon/icons';
} from '@edx/paragon/icons';
import messages from '../messages';
import { useModel } from '../../../../generic/model-store';

View File

@@ -2,8 +2,8 @@ import React from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Blocked } from '@openedx/paragon/icons';
import { Icon } from '@openedx/paragon';
import { Blocked } from '@edx/paragon/icons';
import { Icon } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';
import messages from '../messages';

View File

@@ -5,8 +5,8 @@ import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Icon, IconButton, OverlayTrigger, Popover,
} from '@openedx/paragon';
import { Blocked, InfoOutline } from '@openedx/paragon/icons';
} from '@edx/paragon';
import { Blocked, InfoOutline } from '@edx/paragon/icons';
import messages from '../messages';
import { useModel } from '../../../../generic/model-store';

View File

@@ -5,7 +5,7 @@ import { useSelector } from 'react-redux';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { DataTable } from '@openedx/paragon';
import { DataTable } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';
import AssignmentTypeCell from './AssignmentTypeCell';

View File

@@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { DataTable } from '@openedx/paragon';
import { DataTable } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';
import messages from '../messages';

View File

@@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Hyperlink } from '@openedx/paragon';
import { Hyperlink } from '@edx/paragon';
import messages from './messages';
import { useModel } from '../../../generic/model-store';

Some files were not shown because too many files have changed in this diff Show More