Convert "Pages & Resources" page to a plugin system (#638)
* feat: Make "Pages & Resources" course apps into plugins * feat: move ora_settings * feat: move proctoring * feat: move progress * feat: move teams * feat: move wiki * feat: move Xpert settings * fix: add webpack.prod.config.js * fix: clean up unused parts of package.json files * feat: Add an error message when displaying a Course App Plugin fails * chore: fix various eslint warnings * chore: fix jest tests * fix: error preventing "npm ci" from working * feat: better tests for <SettingsComponent> * chore: move xpert_unit_summary into same dir as other plugins * fix: eslint-import-resolver-webpack is a dev dependency * chore: move learning_assistant to be a plugin too * feat: for compatibility, install 2U plugins by default * fix: bug with learning_assistant package.json
This commit is contained in:
17
.eslintrc.js
17
.eslintrc.js
@@ -1,3 +1,4 @@
|
||||
const path = require('path');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const { createConfig } = require('@openedx/frontend-build');
|
||||
|
||||
@@ -13,5 +14,21 @@ module.exports = createConfig(
|
||||
indent: ['error', 2],
|
||||
'no-restricted-exports': 'off',
|
||||
},
|
||||
settings: {
|
||||
// Import URLs should be resolved using aliases
|
||||
'import/resolver': {
|
||||
webpack: {
|
||||
config: path.resolve(__dirname, 'webpack.dev.config.js'),
|
||||
},
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['plugins/**/*.test.jsx'],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = createConfig('jest', {
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^lodash-es$': 'lodash',
|
||||
'^CourseAuthoring/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
modulePathIgnorePatterns: [
|
||||
'/src/pages-and-resources/utils.test.jsx',
|
||||
|
||||
340
package-lock.json
generated
340
package-lock.json
generated
@@ -23,6 +23,16 @@
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@openedx-plugins/course-app-calculator": "file:plugins/course-apps/calculator",
|
||||
"@openedx-plugins/course-app-edxnotes": "file:plugins/course-apps/edxnotes",
|
||||
"@openedx-plugins/course-app-learning_assistant": "file:plugins/course-apps/learning_assistant",
|
||||
"@openedx-plugins/course-app-live": "file:plugins/course-apps/live",
|
||||
"@openedx-plugins/course-app-ora_settings": "file:plugins/course-apps/ora_settings",
|
||||
"@openedx-plugins/course-app-proctoring": "file:plugins/course-apps/proctoring",
|
||||
"@openedx-plugins/course-app-progress": "file:plugins/course-apps/progress",
|
||||
"@openedx-plugins/course-app-teams": "file:plugins/course-apps/teams",
|
||||
"@openedx-plugins/course-app-wiki": "file:plugins/course-apps/wiki",
|
||||
"@openedx-plugins/course-app-xpert_unit_summary": "file:plugins/course-apps/xpert_unit_summary",
|
||||
"@openedx/paragon": "^21.5.7",
|
||||
"@reduxjs/toolkit": "1.9.7",
|
||||
"@tanstack/react-query": "4.36.1",
|
||||
@@ -65,6 +75,7 @@
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"axios": "^0.27.2",
|
||||
"axios-mock-adapter": "1.22.0",
|
||||
"eslint-import-resolver-webpack": "^0.13.8",
|
||||
"glob": "7.2.3",
|
||||
"husky": "7.0.4",
|
||||
"jest-canvas-mock": "^2.5.2",
|
||||
@@ -4721,6 +4732,46 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-calculator": {
|
||||
"resolved": "plugins/course-apps/calculator",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-edxnotes": {
|
||||
"resolved": "plugins/course-apps/edxnotes",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-learning_assistant": {
|
||||
"resolved": "plugins/course-apps/learning_assistant",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-live": {
|
||||
"resolved": "plugins/course-apps/live",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-ora_settings": {
|
||||
"resolved": "plugins/course-apps/ora_settings",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-proctoring": {
|
||||
"resolved": "plugins/course-apps/proctoring",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-progress": {
|
||||
"resolved": "plugins/course-apps/progress",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-teams": {
|
||||
"resolved": "plugins/course-apps/teams",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-wiki": {
|
||||
"resolved": "plugins/course-apps/wiki",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx-plugins/course-app-xpert_unit_summary": {
|
||||
"resolved": "plugins/course-apps/xpert_unit_summary",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@openedx/frontend-build": {
|
||||
"version": "13.0.27",
|
||||
"resolved": "https://registry.npmjs.org/@openedx/frontend-build/-/frontend-build-13.0.27.tgz",
|
||||
@@ -6622,6 +6673,21 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/array.prototype.find": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.2.tgz",
|
||||
"integrity": "sha512-DRumkfW97iZGOfn+lIXbkVrXL04sfYKX+EfOodo8XboR5sxPDVvOjZTF/rysusa9lmhmSOeD6Vp6RKQP+eP4Tg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.2.0",
|
||||
"es-abstract": "^1.22.1",
|
||||
"es-shim-unscopables": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/array.prototype.flat": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
|
||||
@@ -9436,6 +9502,99 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-webpack": {
|
||||
"version": "0.13.8",
|
||||
"resolved": "https://registry.npmjs.org/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.8.tgz",
|
||||
"integrity": "sha512-Y7WIaXWV+Q21Rz/PJgUxiW/FTBOWmU8NTLdz+nz9mMoiz5vAev/fOaQxwD7qRzTfE3HSm1qsxZ5uRd7eX+VEtA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array.prototype.find": "^2.2.2",
|
||||
"debug": "^3.2.7",
|
||||
"enhanced-resolve": "^0.9.1",
|
||||
"find-root": "^1.1.0",
|
||||
"hasown": "^2.0.0",
|
||||
"interpret": "^1.4.0",
|
||||
"is-core-module": "^2.13.1",
|
||||
"is-regex": "^1.1.4",
|
||||
"lodash": "^4.17.21",
|
||||
"resolve": "^2.0.0-next.5",
|
||||
"semver": "^5.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint-plugin-import": ">=1.4.0",
|
||||
"webpack": ">=1.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-webpack/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-webpack/node_modules/enhanced-resolve": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz",
|
||||
"integrity": "sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"memory-fs": "^0.2.0",
|
||||
"tapable": "^0.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-webpack/node_modules/interpret": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
|
||||
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-webpack/node_modules/resolve": {
|
||||
"version": "2.0.0-next.5",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
|
||||
"integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-webpack/node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-webpack/node_modules/tapable": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz",
|
||||
"integrity": "sha512-jX8Et4hHg57mug1/079yitEKWGB3LCwoxByLsNim89LABq8NqgiX+6iYVOsq0vX8uJHkU+DZ5fnq95f800bEsQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-module-utils": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
|
||||
@@ -10360,6 +10519,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/find-root": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
@@ -14515,6 +14680,12 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/memory-fs": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz",
|
||||
"integrity": "sha512-+y4mDxU4rvXXu5UDSGCGNiesFmwCHuefGMoPCO1WYucNYj7DsLqrFaa2fXVI0H+NNiPTwwzKwspn9yTZqUGqng==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/meow": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
|
||||
@@ -21682,6 +21853,175 @@
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/calculator": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/edxnotes": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/learning_assistant": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/live": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"@reduxjs/toolkit": "*",
|
||||
"lodash": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"react-redux": "*",
|
||||
"react-router-dom": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/ora_settings": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/proctoring": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"classnames": "*",
|
||||
"email-validator": "*",
|
||||
"moment": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/progress": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/teams": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"formik": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"uuid": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/wiki": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins/course-apps/xpert_unit_summary": {
|
||||
"version": "0.1.0",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"formik": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"react-redux": "*",
|
||||
"react-router-dom": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
package.json
11
package.json
@@ -51,6 +51,16 @@
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@openedx-plugins/course-app-calculator": "file:plugins/course-apps/calculator",
|
||||
"@openedx-plugins/course-app-edxnotes": "file:plugins/course-apps/edxnotes",
|
||||
"@openedx-plugins/course-app-live": "file:plugins/course-apps/live",
|
||||
"@openedx-plugins/course-app-ora_settings": "file:plugins/course-apps/ora_settings",
|
||||
"@openedx-plugins/course-app-proctoring": "file:plugins/course-apps/proctoring",
|
||||
"@openedx-plugins/course-app-progress": "file:plugins/course-apps/progress",
|
||||
"@openedx-plugins/course-app-teams": "file:plugins/course-apps/teams",
|
||||
"@openedx-plugins/course-app-wiki": "file:plugins/course-apps/wiki",
|
||||
"@openedx-plugins/course-app-learning_assistant": "file:plugins/course-apps/learning_assistant",
|
||||
"@openedx-plugins/course-app-xpert_unit_summary": "file:plugins/course-apps/xpert_unit_summary",
|
||||
"@reduxjs/toolkit": "1.9.7",
|
||||
"@tanstack/react-query": "4.36.1",
|
||||
"broadcast-channel": "^7.0.0",
|
||||
@@ -92,6 +102,7 @@
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"axios": "^0.27.2",
|
||||
"axios-mock-adapter": "1.22.0",
|
||||
"eslint-import-resolver-webpack": "^0.13.8",
|
||||
"glob": "7.2.3",
|
||||
"husky": "7.0.4",
|
||||
"jest-canvas-mock": "^2.5.2",
|
||||
|
||||
31
plugins/course-apps/calculator/Settings.jsx
Normal file
31
plugins/course-apps/calculator/Settings.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import AppSettingsModal from 'CourseAuthoring/pages-and-resources/app-settings-modal/AppSettingsModal';
|
||||
import messages from './messages';
|
||||
|
||||
/**
|
||||
* Settings widget for the "calculator" Course App.
|
||||
* @param {{onClose: () => void}} props
|
||||
*/
|
||||
const CalculatorSettings = ({ onClose }) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<AppSettingsModal
|
||||
appId="calculator"
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
enableAppHelp={intl.formatMessage(messages.enableCalculatorHelp)}
|
||||
enableAppLabel={intl.formatMessage(messages.enableCalculatorLabel)}
|
||||
learnMoreText={intl.formatMessage(messages.enableCalculatorLink)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
CalculatorSettings.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default CalculatorSettings;
|
||||
17
plugins/course-apps/calculator/package.json
Normal file
17
plugins/course-apps/calculator/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-calculator",
|
||||
"version": "0.1.0",
|
||||
"description": "Calculator configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
31
plugins/course-apps/edxnotes/Settings.jsx
Normal file
31
plugins/course-apps/edxnotes/Settings.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import AppSettingsModal from 'CourseAuthoring/pages-and-resources/app-settings-modal/AppSettingsModal';
|
||||
import messages from './messages';
|
||||
|
||||
/**
|
||||
* Settings widget for the "edxnotes" Course App.
|
||||
* @param {{onClose: () => void}} props
|
||||
*/
|
||||
const NotesSettings = ({ onClose }) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<AppSettingsModal
|
||||
appId="edxnotes"
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
enableAppHelp={intl.formatMessage(messages.enableNotesHelp)}
|
||||
enableAppLabel={intl.formatMessage(messages.enableNotesLabel)}
|
||||
learnMoreText={intl.formatMessage(messages.enableNotesLink)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
NotesSettings.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default NotesSettings;
|
||||
17
plugins/course-apps/edxnotes/package.json
Normal file
17
plugins/course-apps/edxnotes/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-edxnotes",
|
||||
"version": "0.1.0",
|
||||
"description": "edxnotes configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink } from '@openedx/paragon';
|
||||
|
||||
import AppSettingsModal from '../app-settings-modal/AppSettingsModal';
|
||||
import messages from './messages';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import AppSettingsModal from 'CourseAuthoring/pages-and-resources/app-settings-modal/AppSettingsModal';
|
||||
import { useModel } from 'CourseAuthoring/generic/model-store';
|
||||
|
||||
const LearningAssistantSettings = ({ intl, onClose }) => {
|
||||
import messages from './messages';
|
||||
|
||||
const LearningAssistantSettings = ({ onClose }) => {
|
||||
const appId = 'learning_assistant';
|
||||
const appInfo = useModel('courseApps', appId);
|
||||
const intl = useIntl();
|
||||
|
||||
// We need to render more than one link, so we use the bodyChildren prop.
|
||||
const bodyChildren = (
|
||||
@@ -55,8 +57,7 @@ const LearningAssistantSettings = ({ intl, onClose }) => {
|
||||
};
|
||||
|
||||
LearningAssistantSettings.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(LearningAssistantSettings);
|
||||
export default LearningAssistantSettings;
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import { RequestStatus } from 'CourseAuthoring/data/constants';
|
||||
import { render } from 'CourseAuthoring/pages-and-resources/utils.test';
|
||||
import LearningAssistantSettings from './Settings';
|
||||
import { render } from '../utils.test';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
|
||||
const onClose = () => { };
|
||||
|
||||
19
plugins/course-apps/learning_assistant/package.json
Normal file
19
plugins/course-apps/learning_assistant/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-learning_assistant",
|
||||
"version": "0.1.0",
|
||||
"description": "Learning Assistant configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Form, Hyperlink } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import messages from './messages';
|
||||
import AppConfigFormDivider from 'CourseAuthoring/pages-and-resources/discussions/app-config-form/apps/shared/AppConfigFormDivider';
|
||||
import { useModel } from 'CourseAuthoring/generic/model-store';
|
||||
|
||||
import { providerNames, bbbPlanTypes } from './constants';
|
||||
import AppConfigFormDivider from '../discussions/app-config-form/apps/shared/AppConfigFormDivider';
|
||||
import LiveCommonFields from './LiveCommonFields';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import messages from './messages';
|
||||
|
||||
const BbbSettings = ({
|
||||
intl,
|
||||
@@ -15,8 +15,10 @@ import { AppProvider, PageWrap } from '@edx/frontend-platform/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import initializeStore from '../../store';
|
||||
import { executeThunk } from '../../utils';
|
||||
import initializeStore from 'CourseAuthoring/store';
|
||||
import { executeThunk } from 'CourseAuthoring/utils';
|
||||
import PagesAndResourcesProvider from 'CourseAuthoring/pages-and-resources/PagesAndResourcesProvider';
|
||||
|
||||
import LiveSettings from './Settings';
|
||||
import {
|
||||
generateLiveConfigurationApiResponse,
|
||||
@@ -24,11 +26,9 @@ import {
|
||||
initialState,
|
||||
configurationProviders,
|
||||
} from './factories/mockApiResponses';
|
||||
|
||||
import { fetchLiveConfiguration, fetchLiveProviders } from './data/thunks';
|
||||
import { providerConfigurationApiUrl, providersApiUrl } from './data/api';
|
||||
import messages from './messages';
|
||||
import PagesAndResourcesProvider from '../PagesAndResourcesProvider';
|
||||
|
||||
let axiosMock;
|
||||
let container;
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import FormikControl from 'CourseAuthoring/generic/FormikControl';
|
||||
|
||||
import messages from './messages';
|
||||
import FormikControl from '../../generic/FormikControl';
|
||||
|
||||
const LiveCommonFields = ({
|
||||
intl,
|
||||
@@ -6,13 +6,14 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as Yup from 'yup';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import AppSettingsModal from 'CourseAuthoring/pages-and-resources/app-settings-modal/AppSettingsModal';
|
||||
import { useModel } from 'CourseAuthoring/generic/model-store';
|
||||
import Loading from 'CourseAuthoring/generic/Loading';
|
||||
import { RequestStatus } from 'CourseAuthoring/data/constants';
|
||||
|
||||
import { fetchLiveData, saveLiveConfiguration, saveLiveConfigurationAsDraft } from './data/thunks';
|
||||
import { selectApp } from './data/slice';
|
||||
import AppSettingsModal from '../app-settings-modal/AppSettingsModal';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import Loading from '../../generic/Loading';
|
||||
import { iconsSrc, bbbPlanTypes } from './constants';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import messages from './messages';
|
||||
import ZoomSettings from './ZoomSettings';
|
||||
import BBBSettings from './BBBSettings';
|
||||
@@ -18,8 +18,10 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { AppProvider, PageWrap } from '@edx/frontend-platform/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import initializeStore from '../../store';
|
||||
import { executeThunk } from '../../utils';
|
||||
import initializeStore from 'CourseAuthoring/store';
|
||||
import { executeThunk } from 'CourseAuthoring/utils';
|
||||
import PagesAndResourcesProvider from 'CourseAuthoring/pages-and-resources/PagesAndResourcesProvider';
|
||||
|
||||
import LiveSettings from './Settings';
|
||||
import {
|
||||
generateLiveConfigurationApiResponse,
|
||||
@@ -31,7 +33,6 @@ import {
|
||||
import { fetchLiveConfiguration, fetchLiveProviders } from './data/thunks';
|
||||
import { providerConfigurationApiUrl, providersApiUrl } from './data/api';
|
||||
import messages from './messages';
|
||||
import PagesAndResourcesProvider from '../PagesAndResourcesProvider';
|
||||
|
||||
let axiosMock;
|
||||
let container;
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import FormikControl from 'CourseAuthoring/generic/FormikControl';
|
||||
|
||||
import messages from './messages';
|
||||
import { providerNames } from './constants';
|
||||
import LiveCommonFields from './LiveCommonFields';
|
||||
import FormikControl from '../../generic/FormikControl';
|
||||
|
||||
const ZoomSettings = ({
|
||||
intl,
|
||||
@@ -13,8 +13,9 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { AppProvider, PageWrap } from '@edx/frontend-platform/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import initializeStore from '../../store';
|
||||
import { executeThunk } from '../../utils';
|
||||
import initializeStore from 'CourseAuthoring/store';
|
||||
import { executeThunk } from 'CourseAuthoring/utils';
|
||||
import PagesAndResourcesProvider from 'CourseAuthoring/pages-and-resources/PagesAndResourcesProvider';
|
||||
import LiveSettings from './Settings';
|
||||
import {
|
||||
generateLiveConfigurationApiResponse,
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
import { fetchLiveConfiguration, fetchLiveProviders } from './data/thunks';
|
||||
import { providerConfigurationApiUrl, providersApiUrl } from './data/api';
|
||||
import messages from './messages';
|
||||
import PagesAndResourcesProvider from '../PagesAndResourcesProvider';
|
||||
|
||||
let axiosMock;
|
||||
let container;
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { RequestStatus } from '../../../data/constants';
|
||||
import { RequestStatus } from 'CourseAuthoring/data/constants';
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'live',
|
||||
@@ -1,4 +1,6 @@
|
||||
import { addModel, addModels, updateModel } from '../../../generic/model-store';
|
||||
import { addModel, addModels, updateModel } from 'CourseAuthoring/generic/model-store';
|
||||
import { RequestStatus } from 'CourseAuthoring/data/constants';
|
||||
|
||||
import {
|
||||
getLiveConfiguration,
|
||||
getLiveProviders,
|
||||
@@ -7,7 +9,6 @@ import {
|
||||
deNormalizeSettings,
|
||||
} from './api';
|
||||
import { loadApps, updateStatus, updateSaveStatus } from './slice';
|
||||
import { RequestStatus } from '../../../data/constants';
|
||||
|
||||
function updateLiveSettingsState({
|
||||
appConfig,
|
||||
22
plugins/course-apps/live/package.json
Normal file
22
plugins/course-apps/live/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-live",
|
||||
"version": "0.1.0",
|
||||
"description": "Live course configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"@reduxjs/toolkit": "*",
|
||||
"lodash": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"react-redux": "*",
|
||||
"react-router-dom": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import * as Yup from 'yup';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { Hyperlink } from '@openedx/paragon';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import { useModel } from 'CourseAuthoring/generic/model-store';
|
||||
|
||||
import FormSwitchGroup from '../../generic/FormSwitchGroup';
|
||||
import { useAppSetting } from '../../utils';
|
||||
import AppSettingsModal from '../app-settings-modal/AppSettingsModal';
|
||||
import FormSwitchGroup from 'CourseAuthoring/generic/FormSwitchGroup';
|
||||
import { useAppSetting } from 'CourseAuthoring/utils';
|
||||
import AppSettingsModal from 'CourseAuthoring/pages-and-resources/app-settings-modal/AppSettingsModal';
|
||||
import messages from './messages';
|
||||
|
||||
const ORASettings = ({ intl, onClose }) => {
|
||||
@@ -9,14 +9,14 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||
jest.mock('yup', () => ({
|
||||
boolean: jest.fn().mockReturnValue('Yub.boolean'),
|
||||
}));
|
||||
jest.mock('../../generic/model-store', () => ({
|
||||
jest.mock('CourseAuthoring/generic/model-store', () => ({
|
||||
useModel: jest.fn().mockReturnValue({ documentationLinks: { learnMoreConfiguration: 'https://learnmore.test' } }),
|
||||
}));
|
||||
jest.mock('../../generic/FormSwitchGroup', () => 'FormSwitchGroup');
|
||||
jest.mock('../../utils', () => ({
|
||||
jest.mock('CourseAuthoring/generic/FormSwitchGroup', () => 'FormSwitchGroup');
|
||||
jest.mock('CourseAuthoring/utils', () => ({
|
||||
useAppSetting: jest.fn().mockReturnValue(['abitrary value', jest.fn().mockName('saveSetting')]),
|
||||
}));
|
||||
jest.mock('../app-settings-modal/AppSettingsModal', () => 'AppSettingsModal');
|
||||
jest.mock('CourseAuthoring/pages-and-resources/app-settings-modal/AppSettingsModal', () => 'AppSettingsModal');
|
||||
|
||||
const props = {
|
||||
onClose: jest.fn().mockName('onClose'),
|
||||
19
plugins/course-apps/ora_settings/package.json
Normal file
19
plugins/course-apps/ora_settings/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-ora_settings",
|
||||
"version": "0.1.0",
|
||||
"description": "Open Response Assessment configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,16 @@ import {
|
||||
ActionRow, Alert, Badge, Form, Hyperlink, ModalDialog, StatefulButton,
|
||||
} from '@openedx/paragon';
|
||||
|
||||
import ExamsApiService from '../../data/services/ExamsApiService';
|
||||
import StudioApiService from '../../data/services/StudioApiService';
|
||||
import Loading from '../../generic/Loading';
|
||||
import ConnectionErrorAlert from '../../generic/ConnectionErrorAlert';
|
||||
import FormSwitchGroup from '../../generic/FormSwitchGroup';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import PermissionDeniedAlert from '../../generic/PermissionDeniedAlert';
|
||||
import { useIsMobile } from '../../utils';
|
||||
import { PagesAndResourcesContext } from '../PagesAndResourcesProvider';
|
||||
import ExamsApiService from 'CourseAuthoring/data/services/ExamsApiService';
|
||||
import StudioApiService from 'CourseAuthoring/data/services/StudioApiService';
|
||||
import Loading from 'CourseAuthoring/generic/Loading';
|
||||
import ConnectionErrorAlert from 'CourseAuthoring/generic/ConnectionErrorAlert';
|
||||
import FormSwitchGroup from 'CourseAuthoring/generic/FormSwitchGroup';
|
||||
import { useModel } from 'CourseAuthoring/generic/model-store';
|
||||
import PermissionDeniedAlert from 'CourseAuthoring/generic/PermissionDeniedAlert';
|
||||
import { useIsMobile } from 'CourseAuthoring/utils';
|
||||
import { PagesAndResourcesContext } from 'CourseAuthoring/pages-and-resources/PagesAndResourcesProvider';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const ProctoringSettings = ({ intl, onClose }) => {
|
||||
@@ -9,10 +9,10 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import StudioApiService from '../../data/services/StudioApiService';
|
||||
import ExamsApiService from '../../data/services/ExamsApiService';
|
||||
import initializeStore from '../../store';
|
||||
import PagesAndResourcesProvider from '../PagesAndResourcesProvider';
|
||||
import StudioApiService from 'CourseAuthoring/data/services/StudioApiService';
|
||||
import ExamsApiService from 'CourseAuthoring/data/services/ExamsApiService';
|
||||
import initializeStore from 'CourseAuthoring/store';
|
||||
import PagesAndResourcesProvider from 'CourseAuthoring/pages-and-resources/PagesAndResourcesProvider';
|
||||
import ProctoredExamSettings from './Settings';
|
||||
|
||||
const defaultProps = {
|
||||
20
plugins/course-apps/proctoring/package.json
Normal file
20
plugins/course-apps/proctoring/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-proctoring",
|
||||
"version": "0.1.0",
|
||||
"description": "Proctoring configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"classnames": "*",
|
||||
"email-validator": "*",
|
||||
"react": "*",
|
||||
"prop-types": "*",
|
||||
"moment": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import FormSwitchGroup from '../../generic/FormSwitchGroup';
|
||||
import { useAppSetting } from '../../utils';
|
||||
import AppSettingsModal from '../app-settings-modal/AppSettingsModal';
|
||||
import FormSwitchGroup from 'CourseAuthoring/generic/FormSwitchGroup';
|
||||
import { useAppSetting } from 'CourseAuthoring/utils';
|
||||
import AppSettingsModal from 'CourseAuthoring/pages-and-resources/app-settings-modal/AppSettingsModal';
|
||||
import messages from './messages';
|
||||
|
||||
const ProgressSettings = ({ intl, onClose }) => {
|
||||
18
plugins/course-apps/progress/package.json
Normal file
18
plugins/course-apps/progress/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-progress",
|
||||
"version": "0.1.0",
|
||||
"description": "Progress configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button, Form, TransitionReplace } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useState } from 'react';
|
||||
import { GroupTypes, TeamSizes } from '../../data/constants';
|
||||
import { GroupTypes, TeamSizes } from 'CourseAuthoring/data/constants';
|
||||
|
||||
import CollapsableEditor from '../../generic/CollapsableEditor';
|
||||
import FormikControl from '../../generic/FormikControl';
|
||||
import CollapsableEditor from 'CourseAuthoring/generic/CollapsableEditor';
|
||||
import FormikControl from 'CourseAuthoring/generic/FormikControl';
|
||||
import messages from './messages';
|
||||
|
||||
// Maps a team type to its corresponding intl message
|
||||
@@ -7,10 +7,10 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import * as Yup from 'yup';
|
||||
import { GroupTypes, TeamSizes } from '../../data/constants';
|
||||
import FormikControl from '../../generic/FormikControl';
|
||||
import { setupYupExtensions, useAppSetting } from '../../utils';
|
||||
import AppSettingsModal from '../app-settings-modal/AppSettingsModal';
|
||||
import { GroupTypes, TeamSizes } from 'CourseAuthoring/data/constants';
|
||||
import FormikControl from 'CourseAuthoring/generic/FormikControl';
|
||||
import { setupYupExtensions, useAppSetting } from 'CourseAuthoring/utils';
|
||||
import AppSettingsModal from 'CourseAuthoring/pages-and-resources/app-settings-modal/AppSettingsModal';
|
||||
import GroupEditor from './GroupEditor';
|
||||
import messages from './messages';
|
||||
|
||||
20
plugins/course-apps/teams/package.json
Normal file
20
plugins/course-apps/teams/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-teams",
|
||||
"version": "0.1.0",
|
||||
"description": "Teams configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"formik": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"uuid": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
import FormSwitchGroup from '../../generic/FormSwitchGroup';
|
||||
import { useAppSetting } from '../../utils';
|
||||
import AppSettingsModal from '../app-settings-modal/AppSettingsModal';
|
||||
import FormSwitchGroup from 'CourseAuthoring/generic/FormSwitchGroup';
|
||||
import { useAppSetting } from 'CourseAuthoring/utils';
|
||||
import AppSettingsModal from 'CourseAuthoring/pages-and-resources/app-settings-modal/AppSettingsModal';
|
||||
import messages from './messages';
|
||||
|
||||
const WikiSettings = ({ intl, onClose }) => {
|
||||
18
plugins/course-apps/wiki/package.json
Normal file
18
plugins/course-apps/wiki/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-wiki",
|
||||
"version": "0.1.0",
|
||||
"description": "Wiki configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"prop-types": "*",
|
||||
"react": "*",
|
||||
"yup": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
4
plugins/course-apps/xpert_unit_summary/README.rst
Normal file
4
plugins/course-apps/xpert_unit_summary/README.rst
Normal file
@@ -0,0 +1,4 @@
|
||||
Xpert Unit Summaries Configuration Plugin
|
||||
=========================================
|
||||
|
||||
Install this using ``npm install plugins/course-apps/xpert_unit_summary/ --no-save``.
|
||||
@@ -2,8 +2,8 @@ import React, { useCallback, useContext, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { PagesAndResourcesContext } from 'CourseAuthoring/pages-and-resources/PagesAndResourcesProvider';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { PagesAndResourcesContext } from '../PagesAndResourcesProvider';
|
||||
|
||||
import SettingsModal from './settings-modal/SettingsModal';
|
||||
import messages from './messages';
|
||||
@@ -10,12 +10,13 @@ import {
|
||||
queryByTestId, render, waitFor, getByText, fireEvent,
|
||||
} from '@testing-library/react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import PagesAndResourcesProvider from '../PagesAndResourcesProvider';
|
||||
import { XpertUnitSummarySettings } from './index';
|
||||
import initializeStore from '../../store';
|
||||
import PagesAndResourcesProvider from 'CourseAuthoring/pages-and-resources/PagesAndResourcesProvider';
|
||||
import initializeStore from 'CourseAuthoring/store';
|
||||
import { executeThunk } from 'CourseAuthoring/utils';
|
||||
|
||||
import XpertUnitSummarySettings from './Settings';
|
||||
import * as API from './data/api';
|
||||
import * as Thunks from './data/thunks';
|
||||
import { executeThunk } from '../../utils';
|
||||
|
||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||
let axiosMock;
|
||||
@@ -1,12 +1,11 @@
|
||||
import { updateSavingStatus, updateLoadingStatus, updateResetStatus } from 'CourseAuthoring/pages-and-resources/data/slice';
|
||||
import { RequestStatus } from 'CourseAuthoring/data/constants';
|
||||
import { addModel, updateModel } from 'CourseAuthoring/generic/model-store';
|
||||
|
||||
import {
|
||||
getXpertSettings, postXpertSettings, getXpertPluginConfigurable, deleteXpertSettings,
|
||||
} from './api';
|
||||
|
||||
import { updateSavingStatus, updateLoadingStatus, updateResetStatus } from '../../data/slice';
|
||||
import { RequestStatus } from '../../../data/constants';
|
||||
|
||||
import { addModel, updateModel } from '../../../generic/model-store';
|
||||
|
||||
export function updateXpertSettings(courseId, state) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.IN_PROGRESS }));
|
||||
21
plugins/course-apps/xpert_unit_summary/package.json
Normal file
21
plugins/course-apps/xpert_unit_summary/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@openedx-plugins/course-app-xpert_unit_summary",
|
||||
"version": "0.1.0",
|
||||
"description": "Xpert Unit Summaries configuration for courses using it",
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-app-course-authoring": "*",
|
||||
"@edx/frontend-platform": "*",
|
||||
"@openedx/paragon": "*",
|
||||
"formik": "*",
|
||||
"prop-types": "*",
|
||||
"yup": "*",
|
||||
"react": "*",
|
||||
"react-redux": "*",
|
||||
"react-router-dom": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edx/frontend-app-course-authoring": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,22 +24,25 @@ import React, {
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
import { RequestStatus } from '../../../data/constants';
|
||||
import ConnectionErrorAlert from '../../../generic/ConnectionErrorAlert';
|
||||
import FormSwitchGroup from '../../../generic/FormSwitchGroup';
|
||||
import Loading from '../../../generic/Loading';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
import PermissionDeniedAlert from '../../../generic/PermissionDeniedAlert';
|
||||
import { useIsMobile } from '../../../utils';
|
||||
import { getLoadingStatus, getSavingStatus, getResetStatus } from '../../data/selectors';
|
||||
import { updateSavingStatus, updateResetStatus } from '../../data/slice';
|
||||
import { RequestStatus } from 'CourseAuthoring/data/constants';
|
||||
import ConnectionErrorAlert from 'CourseAuthoring/generic/ConnectionErrorAlert';
|
||||
import FormSwitchGroup from 'CourseAuthoring/generic/FormSwitchGroup';
|
||||
import Loading from 'CourseAuthoring/generic/Loading';
|
||||
import { useModel } from 'CourseAuthoring/generic/model-store';
|
||||
import PermissionDeniedAlert from 'CourseAuthoring/generic/PermissionDeniedAlert';
|
||||
import { useIsMobile } from 'CourseAuthoring/utils';
|
||||
import { getLoadingStatus, getSavingStatus, getResetStatus } from 'CourseAuthoring/pages-and-resources/data/selectors';
|
||||
import { updateSavingStatus, updateResetStatus } from 'CourseAuthoring/pages-and-resources/data/slice';
|
||||
import AppConfigFormDivider from 'CourseAuthoring/pages-and-resources/discussions/app-config-form/apps/shared/AppConfigFormDivider';
|
||||
import { PagesAndResourcesContext } from 'CourseAuthoring/pages-and-resources/PagesAndResourcesProvider';
|
||||
|
||||
import { updateXpertSettings, resetXpertSettings, removeXpertSettings } from '../data/thunks';
|
||||
import AppConfigFormDivider from '../../discussions/app-config-form/apps/shared/AppConfigFormDivider';
|
||||
import { PagesAndResourcesContext } from '../../PagesAndResourcesProvider';
|
||||
import messages from './messages';
|
||||
import appInfo from '../appInfo';
|
||||
import ResetIcon from './ResetIcon';
|
||||
|
||||
import './SettingsModal.scss';
|
||||
|
||||
const AppSettingsForm = ({
|
||||
formikProps, children, showForm,
|
||||
}) => children && (
|
||||
@@ -1,3 +1,6 @@
|
||||
@import "~@edx/brand/paragon/variables";
|
||||
@import "~@openedx/paragon/scss/core/utilities-only";
|
||||
|
||||
.summary-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -13,7 +13,6 @@
|
||||
@import "studio-home/scss/StudioHome";
|
||||
@import "generic/styles";
|
||||
@import "schedule-and-details/ScheduleAndDetails";
|
||||
@import "pages-and-resources/PagesAndResources";
|
||||
@import "course-team/CourseTeam";
|
||||
@import "course-updates/CourseUpdates";
|
||||
@import "export-page/CourseExportPage";
|
||||
|
||||
@@ -5,17 +5,10 @@ import { PageWrap, AppContext } from '@edx/frontend-platform/react';
|
||||
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Button, Hyperlink } from '@openedx/paragon';
|
||||
import messages from './messages';
|
||||
import DiscussionsSettings from './discussions';
|
||||
import {
|
||||
XpertUnitSummarySettings,
|
||||
fetchXpertPluginConfigurable,
|
||||
fetchXpertSettings,
|
||||
appInfo as XpertAppInfo,
|
||||
} from './xpert-unit-summary';
|
||||
|
||||
import PageGrid from './pages/PageGrid';
|
||||
import { fetchCourseApps } from './data/thunks';
|
||||
@@ -34,8 +27,6 @@ const PagesAndResources = ({ courseId, intl }) => {
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(fetchCourseApps(courseId));
|
||||
dispatch(fetchXpertPluginConfigurable(courseId));
|
||||
dispatch(fetchXpertSettings(courseId));
|
||||
}, [courseId]);
|
||||
|
||||
const courseAppIds = useSelector(state => state.pagesAndResources.courseAppIds);
|
||||
@@ -46,32 +37,21 @@ const PagesAndResources = ({ courseId, intl }) => {
|
||||
const learningCourseURL = `${config.LEARNING_BASE_URL}/course/${courseId}`;
|
||||
const redirectUrl = `/course/${courseId}/pages-and-resources`;
|
||||
|
||||
// Most pages here are driven by a course app. The one exception is the Xpert unit summaries page.
|
||||
// The pages here are driven by course apps. The list of course app IDs comes from the LMS API.
|
||||
// We display all enabled course apps regardless of whether or not the corresponding frontend plugin is available.
|
||||
const pages = useModels('courseApps', courseAppIds);
|
||||
const xpertPluginConfigurable = useModel('XpertSettings.enabled', 'xpert-unit-summary');
|
||||
const xpertSettings = useModel('XpertSettings', 'xpert-unit-summary');
|
||||
|
||||
// These pages appear in a separate "Content Permissions" section at the bottom of the page.
|
||||
// If there are no content permission pages, this section will not appear.
|
||||
// We want the Xpert learning assistant and unit summaries to appear in the "Content Permissions" section instead,
|
||||
// so we remove them from pages and add them to contentPermissionsPages.
|
||||
const contentPermissionsPages = [];
|
||||
|
||||
// Xpert unit summaries
|
||||
if (xpertPluginConfigurable?.enabled) {
|
||||
contentPermissionsPages.push({
|
||||
...XpertAppInfo,
|
||||
enabled: xpertSettings?.enabled !== undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// Xpert learning assistant
|
||||
if (_.some(pages, (page) => page.id === 'learning_assistant')) {
|
||||
const index = pages.findIndex(app => app.id === 'learning_assistant');
|
||||
|
||||
// We want the Xpert learning assistant page to appear in the "Content Permissions" section instead,
|
||||
// so we remove it from pages and add it to contentPermissionsPages.
|
||||
const [page] = pages.splice(index, 1);
|
||||
contentPermissionsPages.push(page);
|
||||
}
|
||||
['xpert_unit_summary', 'learning_assistant'].forEach(separateAppId => {
|
||||
const index = pages.findIndex(app => app.id === separateAppId);
|
||||
if (index !== -1) {
|
||||
const [page] = pages.splice(index, 1);
|
||||
contentPermissionsPages.push(page);
|
||||
}
|
||||
});
|
||||
|
||||
if (loadingStatus === RequestStatus.IN_PROGRESS) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
@@ -99,10 +79,16 @@ const PagesAndResources = ({ courseId, intl }) => {
|
||||
</Hyperlink>
|
||||
</div>
|
||||
|
||||
<PageGrid pages={pages} />
|
||||
<Routes>
|
||||
<Route path="discussion/configure/:appId" element={<PageWrap><DiscussionsSettings courseId={courseId} /></PageWrap>} />
|
||||
<Route path="discussion" element={<PageWrap><DiscussionsSettings courseId={courseId} /></PageWrap>} />
|
||||
<Route path="discussion/settings" element={<PageWrap><DiscussionsSettings courseId={courseId} /></PageWrap>} />
|
||||
<Route path=":appId/settings" element={<PageWrap><Suspense fallback="..."><SettingsComponent url={redirectUrl} /></Suspense></PageWrap>} />
|
||||
</Routes>
|
||||
|
||||
<PageGrid pages={pages} />
|
||||
{
|
||||
!_.isEmpty(contentPermissionsPages) && (
|
||||
(contentPermissionsPages.length > 0) && (
|
||||
<>
|
||||
<div className="d-flex justify-content-between my-4 my-md-5 align-items-center">
|
||||
<h3 className="m-0">{intl.formatMessage(messages.contentPermissions)}</h3>
|
||||
@@ -111,14 +97,6 @@ const PagesAndResources = ({ courseId, intl }) => {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
<Routes>
|
||||
<Route path="discussion/configure/:appId" element={<PageWrap><DiscussionsSettings courseId={courseId} /></PageWrap>} />
|
||||
<Route path="discussion" element={<PageWrap><DiscussionsSettings courseId={courseId} /></PageWrap>} />
|
||||
<Route path="discussion/settings" element={<PageWrap><DiscussionsSettings courseId={courseId} /></PageWrap>} />
|
||||
<Route path="xpert-unit-summary/settings" element={<PageWrap><XpertUnitSummarySettings courseId={courseId} /></PageWrap>} />
|
||||
<Route path=":appId/settings" element={<PageWrap><Suspense fallback="..."><SettingsComponent url={redirectUrl} /></Suspense></PageWrap>} />
|
||||
</Routes>
|
||||
</main>
|
||||
</PagesAndResourcesProvider>
|
||||
);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
@import "./xpert-unit-summary/settings-modal/SettingsModal";
|
||||
@@ -1,10 +1,7 @@
|
||||
import { camelCaseObject } from '@edx/frontend-platform';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import { PagesAndResources } from '.';
|
||||
import * as pagesAndResourcesApi from './data/api';
|
||||
import { render } from './utils.test';
|
||||
import * as xpertUnitSummaryApi from './xpert-unit-summary/data/api';
|
||||
|
||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||
|
||||
@@ -14,56 +11,80 @@ describe('PagesAndResources', () => {
|
||||
});
|
||||
|
||||
it('doesn\'t show content permissions section if relevant apps are not enabled', () => {
|
||||
jest.spyOn(pagesAndResourcesApi, 'getCourseApps').mockResolvedValue(camelCaseObject([]));
|
||||
|
||||
const apiResponse = { response: { enabled: true } };
|
||||
jest.spyOn(xpertUnitSummaryApi, 'getXpertSettings').mockResolvedValue(apiResponse);
|
||||
jest.spyOn(xpertUnitSummaryApi, 'getXpertPluginConfigurable').mockResolvedValue(apiResponse);
|
||||
const initialState = {
|
||||
models: {
|
||||
courseApps: {},
|
||||
},
|
||||
pagesAndResources: {
|
||||
courseAppIds: [],
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<PagesAndResources
|
||||
courseId={courseId}
|
||||
/>,
|
||||
<PagesAndResources courseId={courseId} />,
|
||||
{ preloadedState: initialState },
|
||||
);
|
||||
|
||||
expect(screen.queryByRole('heading', { name: 'Content permissions' })).not.toBeInTheDocument();
|
||||
});
|
||||
it('show content permissions section if Learning Assistant app is enabled', async () => {
|
||||
const apiResponse = [
|
||||
{
|
||||
id: 'learning_assistant',
|
||||
enabled: true,
|
||||
name: 'Learning Assistant',
|
||||
description: 'Learning Assistant description',
|
||||
allowed_operations: {
|
||||
configure: false,
|
||||
enable: true,
|
||||
const initialState = {
|
||||
models: {
|
||||
courseApps: {
|
||||
learning_assistant: {
|
||||
id: 'learning_assistant',
|
||||
enabled: true,
|
||||
name: 'Learning Assistant',
|
||||
description: 'Learning Assistant description',
|
||||
allowedOperations: {
|
||||
configure: false,
|
||||
enable: true,
|
||||
},
|
||||
documentationLinks: {},
|
||||
},
|
||||
},
|
||||
documentation_links: {},
|
||||
},
|
||||
];
|
||||
|
||||
jest.spyOn(pagesAndResourcesApi, 'getCourseApps').mockResolvedValue(camelCaseObject(apiResponse));
|
||||
pagesAndResources: {
|
||||
courseAppIds: ['learning_assistant'],
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<PagesAndResources
|
||||
courseId={courseId}
|
||||
/>,
|
||||
<PagesAndResources courseId={courseId} />,
|
||||
{ preloadedState: initialState },
|
||||
);
|
||||
|
||||
await waitFor(() => expect(screen.getByRole('heading', { name: 'Content permissions' })).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.getByText('Learning Assistant')).toBeInTheDocument());
|
||||
});
|
||||
it('show content permissions section if Xpert learning summaries app is enabled', async () => {
|
||||
const apiResponse = { response: { enabled: true } };
|
||||
|
||||
jest.spyOn(xpertUnitSummaryApi, 'getXpertSettings').mockResolvedValue(apiResponse);
|
||||
jest.spyOn(xpertUnitSummaryApi, 'getXpertPluginConfigurable').mockResolvedValue(apiResponse);
|
||||
it('show content permissions section if Xpert learning summaries app is enabled', async () => {
|
||||
const initialState = {
|
||||
models: {
|
||||
courseApps: {
|
||||
xpert_unit_summary: {
|
||||
id: 'xpert_unit_summary',
|
||||
enabled: false,
|
||||
name: 'Xpert unit summaries',
|
||||
description: 'Use generative AI to summarize course content and reinforce learning.',
|
||||
allowedOperations: {
|
||||
enable: true,
|
||||
configure: true,
|
||||
},
|
||||
documentationLinks: {
|
||||
learnMoreConfiguration: 'https://openai.com/',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pagesAndResources: {
|
||||
courseAppIds: ['xpert_unit_summary'],
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<PagesAndResources
|
||||
courseId={courseId}
|
||||
/>,
|
||||
<PagesAndResources courseId={courseId} />,
|
||||
{ preloadedState: initialState },
|
||||
);
|
||||
|
||||
await waitFor(() => expect(screen.getByRole('heading', { name: 'Content permissions' })).toBeInTheDocument());
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { ErrorAlert } from '@edx/frontend-lib-content-components';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const PluginLoadFailedError = () => {
|
||||
const intl = useIntl();
|
||||
return <ErrorAlert isError>{intl.formatMessage(messages.errorShowingConfiguration)}</ErrorAlert>;
|
||||
};
|
||||
|
||||
const SettingsComponent = ({ url }) => {
|
||||
const { appId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const LazyLoadedComponent = React.lazy(async () => {
|
||||
try {
|
||||
// There seems to be a bug in babel-eslint that causes the checker to crash with the following error
|
||||
// if we use a template string here:
|
||||
// TypeError: Cannot read property 'range' of null with using template strings here.
|
||||
// Ref: https://github.com/babel/babel-eslint/issues/530
|
||||
return await import(`./${appId}/Settings.jsx`);
|
||||
} catch (error) {
|
||||
console.trace(error); // eslint-disable-line no-console
|
||||
return null;
|
||||
}
|
||||
});
|
||||
const LazyLoadedComponent = React.useMemo(
|
||||
() => React.lazy(() =>
|
||||
import(`@openedx-plugins/course-app-${appId}/Settings.jsx`).catch((err) => { // eslint-disable-line
|
||||
// If we couldn't load this plugin, log the details to the console.
|
||||
console.trace(err); // eslint-disable-line no-console
|
||||
return { default: PluginLoadFailedError };
|
||||
})),
|
||||
[appId],
|
||||
);
|
||||
|
||||
return <LazyLoadedComponent onClose={() => navigate(url)} />;
|
||||
};
|
||||
|
||||
@@ -1,26 +1,83 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { initializeMockApp } from '@edx/frontend-platform/testing';
|
||||
|
||||
import PagesAndResourcesProvider from 'CourseAuthoring/pages-and-resources/PagesAndResourcesProvider';
|
||||
import initializeStore from 'CourseAuthoring/store';
|
||||
import { RequestStatus } from 'CourseAuthoring/data/constants';
|
||||
import SettingsComponent from './SettingsComponent';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: () => ({
|
||||
appId: 'wiki',
|
||||
}),
|
||||
useParams: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('CourseAuthoring/utils', () => ({
|
||||
useAppSetting: () => [false, () => undefined],
|
||||
useIsMobile: () => false,
|
||||
}));
|
||||
|
||||
let store;
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const RequiredProviders = ({ children }) => (
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<AppProvider store={store}>
|
||||
<PagesAndResourcesProvider courseId="course-v1:foo+bar+baz">
|
||||
{children}
|
||||
</PagesAndResourcesProvider>
|
||||
</AppProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
describe('SettingsComponent', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp();
|
||||
store = initializeStore({
|
||||
models: {
|
||||
courseApps: {
|
||||
wiki: {},
|
||||
},
|
||||
},
|
||||
pagesAndResources: {
|
||||
loadingStatus: RequestStatus.SUCCESSFUL,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('renders LazyLoadedComponent when provided with props', async () => {
|
||||
const { asFragment } = render(
|
||||
<BrowserRouter>
|
||||
<Suspense fallback="...">
|
||||
<SettingsComponent url="/some-url" />
|
||||
</Suspense>
|
||||
</BrowserRouter>,
|
||||
useParams.mockImplementation(() => ({ appId: 'wiki' }));
|
||||
|
||||
const rendered = render(
|
||||
<Suspense fallback="...">
|
||||
<SettingsComponent url="/some-url" />
|
||||
</Suspense>,
|
||||
{ wrapper: RequiredProviders },
|
||||
);
|
||||
|
||||
expect(asFragment).toMatchSnapshot();
|
||||
await waitFor(() => expect(rendered.getByText('Configure wiki')).toBeInTheDocument());
|
||||
|
||||
const modalComponent = screen.getByRole('dialog');
|
||||
expect(modalComponent.querySelector('#enable-wiki-toggleHelpText')).toContainHTML('The course wiki can be set up');
|
||||
});
|
||||
|
||||
test('renders error message when plugin is unavilable when provided with props', async () => {
|
||||
// Silence noisy error about the plugin failing to load, when we do that deliberately.
|
||||
jest.spyOn(console, 'trace').mockImplementation(() => {});
|
||||
// Specify an invalid course app, with no matching plugin:
|
||||
useParams.mockImplementation(() => ({ appId: 'invalid-plugin' }));
|
||||
|
||||
const rendered = render(
|
||||
<Suspense fallback="...">
|
||||
<SettingsComponent url="/some-url" />
|
||||
</Suspense>,
|
||||
{ wrapper: RequiredProviders },
|
||||
);
|
||||
|
||||
const errorMessage = 'An error occurred when loading the configuration UI';
|
||||
await waitFor(() => expect(rendered.container).toHaveTextContent(errorMessage));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SettingsComponent renders LazyLoadedComponent when provided with props 1`] = `[Function]`;
|
||||
@@ -126,6 +126,8 @@ const AppSettingsModal = ({
|
||||
const updateSettingsRequestStatus = useSelector(getSavingStatus);
|
||||
const alertRef = useRef(null);
|
||||
const [saveError, setSaveError] = useState(false);
|
||||
// FIXME: open the "Live" settings, then refresh the page. The courseApps model is not loaded, and an error occurs
|
||||
// when trying to access 'appInfo.documentationLinks'. This happens even before the refactor to use plugins.
|
||||
const appInfo = useModel('courseApps', appId);
|
||||
const dispatch = useDispatch();
|
||||
const submitButtonState = updateSettingsRequestStatus === RequestStatus.IN_PROGRESS ? 'pending' : 'default';
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import AppSettingsModal from '../app-settings-modal/AppSettingsModal';
|
||||
import messages from './messages';
|
||||
|
||||
const CalculatorSettings = ({ intl, onClose }) => (
|
||||
<AppSettingsModal
|
||||
appId="calculator"
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
enableAppHelp={intl.formatMessage(messages.enableCalculatorHelp)}
|
||||
enableAppLabel={intl.formatMessage(messages.enableCalculatorLabel)}
|
||||
learnMoreText={intl.formatMessage(messages.enableCalculatorLink)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
CalculatorSettings.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(CalculatorSettings);
|
||||
@@ -6,8 +6,8 @@ import React, { useState } from 'react';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
import * as Yup from 'yup';
|
||||
import { useModel, useModels } from '../../../../../generic/model-store';
|
||||
import { setupYupExtensions } from '../../../../../utils';
|
||||
import { useModel, useModels } from 'CourseAuthoring/generic/model-store';
|
||||
import { setupYupExtensions } from 'CourseAuthoring/utils';
|
||||
import messages from '../../messages';
|
||||
import { checkFieldErrors } from '../../utils';
|
||||
import AnonymousPostingFields from '../shared/AnonymousPostingFields';
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import AppSettingsModal from '../app-settings-modal/AppSettingsModal';
|
||||
import messages from './messages';
|
||||
|
||||
const NotesSettings = ({ intl, onClose }) => (
|
||||
<AppSettingsModal
|
||||
appId="edxnotes"
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
enableAppHelp={intl.formatMessage(messages.enableNotesHelp)}
|
||||
enableAppLabel={intl.formatMessage(messages.enableNotesLabel)}
|
||||
learnMoreText={intl.formatMessage(messages.enableNotesLink)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
NotesSettings.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(NotesSettings);
|
||||
@@ -13,6 +13,10 @@ const messages = defineMessages({
|
||||
id: 'course-authoring.pages-resources.viewLive.button',
|
||||
defaultMessage: 'View live',
|
||||
},
|
||||
errorShowingConfiguration: {
|
||||
id: 'course-authoring.pages-resources.courseAppPlugin.errorMessage',
|
||||
defaultMessage: 'An error occurred when loading the configuration UI for that app.',
|
||||
},
|
||||
enabled: {
|
||||
id: 'course-authoring.badge.enabled',
|
||||
defaultMessage: 'Enabled',
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import XpertUnitSummarySettings from './XpertUnitSummarySettings';
|
||||
import appInfo from './appInfo';
|
||||
import { fetchXpertPluginConfigurable, fetchXpertSettings } from './data/thunks';
|
||||
|
||||
export {
|
||||
XpertUnitSummarySettings,
|
||||
appInfo,
|
||||
fetchXpertPluginConfigurable,
|
||||
fetchXpertSettings,
|
||||
};
|
||||
@@ -1,15 +1,18 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
// FIXME: because the 'live' plugin is using Redux, we have to hard-code a reference to it here.
|
||||
// If this app + the plugin were using React-query, there'd be no issues.
|
||||
import { reducer as liveReducer } from '@openedx-plugins/course-app-live/data/slice';
|
||||
|
||||
import { reducer as modelsReducer } from './generic/model-store';
|
||||
import { reducer as courseDetailReducer } from './data/slice';
|
||||
import { reducer as discussionsReducer } from './pages-and-resources/discussions';
|
||||
import { reducer as discussionsReducer } from './pages-and-resources/discussions/data/slice';
|
||||
import { reducer as pagesAndResourcesReducer } from './pages-and-resources/data/slice';
|
||||
import { reducer as customPagesReducer } from './custom-pages/data/slice';
|
||||
import { reducer as advancedSettingsReducer } from './advanced-settings/data/slice';
|
||||
import { reducer as gradingSettingsReducer } from './grading-settings/data/slice';
|
||||
import { reducer as studioHomeReducer } from './studio-home/data/slice';
|
||||
import { reducer as scheduleAndDetailsReducer } from './schedule-and-details/data/slice';
|
||||
import { reducer as liveReducer } from './pages-and-resources/live/data/slice';
|
||||
import { reducer as filesReducer } from './files-and-videos/files-page/data/slice';
|
||||
import { reducer as courseTeamReducer } from './course-team/data/slice';
|
||||
import { reducer as CourseUpdatesReducer } from './course-updates/data/slice';
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
const path = require('path');
|
||||
const { createConfig } = require('@openedx/frontend-build');
|
||||
|
||||
const config = createConfig('webpack-dev', {
|
||||
resolve: {
|
||||
alias: {
|
||||
// Plugins can use 'CourseAuthoring' as an import alias for this app:
|
||||
CourseAuthoring: path.resolve(__dirname, 'src/'),
|
||||
},
|
||||
fallback: {
|
||||
fs: false,
|
||||
constants: false,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
const path = require('path');
|
||||
const { createConfig } = require('@openedx/frontend-build');
|
||||
|
||||
const config = createConfig('webpack-prod', {
|
||||
resolve: {
|
||||
alias: {
|
||||
// Plugins can use 'CourseAuthoring' as an import alias for this app:
|
||||
CourseAuthoring: path.resolve(__dirname, 'src/'),
|
||||
},
|
||||
fallback: {
|
||||
fs: false,
|
||||
constants: false,
|
||||
|
||||
Reference in New Issue
Block a user