Compare commits

...

22 Commits

Author SHA1 Message Date
Maxwell Frank
67dee2c96b Merge branch 'master' into mfrank/remove-hotjar 2026-01-08 14:06:33 -05:00
renovate[bot]
0a50937b4c chore(deps): update dependency @openedx/paragon to v23.18.2 (#771)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-22 05:05:47 +00:00
dependabot[bot]
22a1c658f1 chore(deps): bump actions/checkout from 5 to 6 (#750)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Maxwell Frank <92897870+MaxFrank13@users.noreply.github.com>
2025-12-18 09:00:20 -05:00
Maxwell Frank
75396f1dab fix(deps): remove filesize dependency (#767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-18 08:55:13 -05:00
Ejaz Ahmad
62099a50eb fix: env variables fetching issue for translations (#766) 2025-12-17 08:47:23 -05:00
renovate[bot]
19ccb8ab87 chore(deps): update dependency @reduxjs/toolkit to v2.11.2 (#761)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 06:31:30 +00:00
renovate[bot]
a3e2c80537 chore(deps): update dependency @reduxjs/toolkit to v2.11.1 (#756)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 12:40:25 +00:00
renovate[bot]
324cb525c6 fix(deps): update dependency core-js to v3.47.0 (#757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 08:39:38 +00:00
renovate[bot]
f14ab8851d chore(deps): update dependency @openedx/paragon to v23.18.1 (#755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 05:37:57 +00:00
Ejaz Ahmad
c277150716 feat: added the ability for instances to use local translations from extra repositories (#752) 2025-12-03 08:31:14 -05:00
renovate[bot]
2a0ed5714f chore(deps): update dependency @reduxjs/toolkit to v2.11.0 (#749)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 09:04:15 +00:00
renovate[bot]
1f0b758705 chore(deps): update dependency @openedx/paragon to v23.18.0 (#748)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 05:30:08 +00:00
renovate[bot]
85a5a6e94e chore(deps): update dependency @openedx/paragon to v23.17.0 (#746)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 09:13:57 +00:00
renovate[bot]
f59b5013c8 fix(deps): update dependency react-router-dom to v6.30.2 (#745)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 06:51:01 +00:00
Maxwell Frank
86b6574c60 [DEPR] feat!: remove notices wrapper (#731) 2025-11-13 12:19:12 -05:00
renovate[bot]
c38e80505c fix(deps): update dependency @edx/frontend-component-header to v8 (#744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 12:57:02 -05:00
renovate[bot]
b926e13c01 chore(deps): update dependency @reduxjs/toolkit to v2.10.1 (#743)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 05:33:11 +00:00
renovate[bot]
0a4285aad3 chore(deps): update dependency @openedx/paragon to v23.16.0 (#742)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 21:53:16 +00:00
renovate[bot]
e70fa29261 chore(deps): update dependency @reduxjs/toolkit to v2.9.2 (#741)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 19:17:47 +00:00
renovate[bot]
b6ba8fb366 chore(deps): update dependency @edx/frontend-platform to v8.5.2 (#740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 08:45:29 +00:00
renovate[bot]
2221655950 chore(deps): update dependency @edx/frontend-component-footer to v14.9.3 (#739)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 07:03:55 +00:00
MaxFrank13
35b142f2cd feat: remove hotjar 2025-09-11 15:15:20 +00:00
20 changed files with 53 additions and 386 deletions

1
.env
View File

@@ -37,7 +37,6 @@ HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
ACCOUNT_SETTINGS_URL=''
ACCOUNT_PROFILE_URL=''
ENABLE_NOTICES=''
CAREER_LINK_URL=''
ENABLE_EDX_PERSONAL_DASHBOARD=false
ENABLE_PROGRAMS=false

View File

@@ -43,7 +43,6 @@ HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
ACCOUNT_SETTINGS_URL='http://localhost:1997'
ACCOUNT_PROFILE_URL='http://localhost:1995'
ENABLE_NOTICES=''
CAREER_LINK_URL=''
ENABLE_EDX_PERSONAL_DASHBOARD=false
ENABLE_PROGRAMS=false

View File

@@ -42,7 +42,6 @@ HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
ACCOUNT_SETTINGS_URL='http://account-settings-url.test'
ACCOUNT_PROFILE_URL='http://account-profile-url.test'
ENABLE_NOTICES=''
CAREER_LINK_URL=''
ENABLE_EDX_PERSONAL_DASHBOARD=true
ENABLE_PROGRAMS=false

View File

@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup Nodejs
uses: actions/setup-node@v6

View File

@@ -12,6 +12,11 @@ transifex_temp = ./temp/babel-plugin-formatjs
NPM_TESTS=build i18n_extract lint test
# Variables for additional translation sources and imports (define in edx-internal if needed)
ATLAS_EXTRA_SOURCES ?=
ATLAS_EXTRA_INTL_IMPORTS ?=
ATLAS_OPTIONS ?=
.PHONY: test
test: $(addprefix test.npm.,$(NPM_TESTS)) ## validate ci suite
@@ -48,9 +53,10 @@ pull_translations:
translations/frontend-platform/src/i18n/messages:frontend-platform \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-app-learner-dashboard/src/i18n/messages:frontend-app-learner-dashboard
translations/frontend-app-learner-dashboard/src/i18n/messages:frontend-app-learner-dashboard \
$(ATLAS_EXTRA_SOURCES)
$(intl_imports) frontend-platform paragon frontend-component-footer frontend-app-learner-dashboard
$(intl_imports) frontend-platform paragon frontend-component-footer frontend-app-learner-dashboard $(ATLAS_EXTRA_INTL_IMPORTS)
# This target is used by CI.
validate-no-uncommitted-package-lock-changes:

View File

@@ -67,7 +67,6 @@ module.exports = {
NEW_RELIC_LICENSE_KEY: '',
ACCOUNT_SETTINGS_URL: 'http://localhost:1997',
ACCOUNT_PROFILE_URL: 'http://localhost:1995',
ENABLE_NOTICES: '',
CAREER_LINK_URL: '',
EXPERIMENT_08_23_VAN_PAINTED_DOOR: true,
};

84
package-lock.json generated
View File

@@ -11,7 +11,7 @@
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-header": "^6.6.0",
"@edx/frontend-component-header": "^8.0.0",
"@edx/frontend-enterprise-hotjar": "7.2.0",
"@edx/frontend-platform": "^8.3.1",
"@edx/openedx-atlas": "^0.7.0",
@@ -24,8 +24,7 @@
"@redux-devtools/extension": "3.3.0",
"@reduxjs/toolkit": "^2.0.0",
"classnames": "^2.3.1",
"core-js": "3.46.0",
"filesize": "^10.0.0",
"core-js": "3.47.0",
"font-awesome": "4.7.0",
"history": "5.3.0",
"lodash": "^4.17.21",
@@ -36,7 +35,7 @@
"react-helmet": "^6.1.0",
"react-intl": "6.8.9",
"react-redux": "^7.2.4",
"react-router-dom": "6.30.1",
"react-router-dom": "6.30.2",
"react-share": "^4.4.0",
"redux": "4.2.1",
"redux-logger": "3.0.6",
@@ -2349,9 +2348,9 @@
}
},
"node_modules/@edx/frontend-component-footer": {
"version": "14.9.2",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-14.9.2.tgz",
"integrity": "sha512-koYtfZK9flTO3ExAmaP0HDlxbV9XX8hbRE/8WNtMJh+X1B8xppT3Ft8vhGDsw6dEBo9ojndmU9805G/a8/8o3g==",
"version": "14.9.3",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-14.9.3.tgz",
"integrity": "sha512-cr2blXNMBdrFHPvfsoNAaI2KtEUKIufQE3si7LfBDU3VQu/pPBePi99hVCgCpjIOVHUC5TA/t0SzFD5i1RDrvw==",
"license": "AGPL-3.0",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.7.2",
@@ -2419,9 +2418,9 @@
}
},
"node_modules/@edx/frontend-component-header": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-6.6.1.tgz",
"integrity": "sha512-ETGIpCyXq1YWR/wvc4fGzPUtGsdYfXKDtuH45sgiRx7Zt9spgNm0KYO1tah1TF1UrPjIkQErm+8LFh8me/kJCg==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-8.1.0.tgz",
"integrity": "sha512-WkRTbEpbGsDdk74utLVOeCQLjb5fxhHMwL5xkbHfex7sGw+BxZGzQlrbZ9qv957pEano9S0kyg1SbUl7zqZhjQ==",
"license": "AGPL-3.0",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.7.2",
@@ -2500,9 +2499,9 @@
}
},
"node_modules/@edx/frontend-platform": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-8.5.1.tgz",
"integrity": "sha512-8u3EdO0o7xX4vqorjOx3k2wbs2bu3DXlIA3bnD+Y56vSB5QYw6k5GzYqo9pPaTMGeq9TuLRvPLE/QFFlc3xvPg==",
"version": "8.5.2",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-8.5.2.tgz",
"integrity": "sha512-YlxNWs8NW/I7F03k/jH6grWIuY/GJrspq7fqWm5K0ocvNEf+B8XKcaLUof+jVUuCItK93SoVRDZewwejnjty5w==",
"license": "AGPL-3.0",
"peer": true,
"dependencies": {
@@ -4747,9 +4746,9 @@
}
},
"node_modules/@openedx/paragon": {
"version": "23.14.9",
"resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.14.9.tgz",
"integrity": "sha512-IwM6UZJE6lKmGCyGV52oO+40m0y2kFAoOdzyFdw3ud+Wc0Ro3bcflPnuNuq2Wq5a3ceJ9RLu9g+uKx0Yv9yvrw==",
"version": "23.18.2",
"resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.18.2.tgz",
"integrity": "sha512-/mQq0Wf2JL4m7kMpECluXYQVanqmJUwPyjL7e7OBK0vPne/4a5fbQ2cq0wqrZ0ROLk/5f2jvc9V6wrKPd1Xo1A==",
"license": "Apache-2.0",
"peer": true,
"workspaces": [
@@ -5333,14 +5332,14 @@
}
},
"node_modules/@reduxjs/toolkit": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.1.tgz",
"integrity": "sha512-sETJ3qO72y7L7WiR5K54UFLT3jRzAtqeBPVO15xC3bGA6kDqCH8m/v7BKCPH4czydXzz/1lPEGLvew7GjOO3Qw==",
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
"integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@standard-schema/utils": "^0.3.0",
"immer": "^10.0.3",
"immer": "^11.0.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
@@ -5381,9 +5380,9 @@
"license": "MIT"
},
"node_modules/@remix-run/router": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
"version": "1.23.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz",
"integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -9054,9 +9053,9 @@
}
},
"node_modules/core-js": {
"version": "3.46.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz",
"integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==",
"version": "3.47.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz",
"integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
@@ -11806,15 +11805,6 @@
"node": ">=10"
}
},
"node_modules/filesize": {
"version": "10.1.6",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
"integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 10.4.0"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -13213,9 +13203,9 @@
"license": "MIT"
},
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/immer/-/immer-11.0.0.tgz",
"integrity": "sha512-XtRG4SINt4dpqlnJvs70O2j6hH7H0X8fUzFsjMn1rwnETaxwp83HLNimXBjZ78MrKl3/d3/pkzDH0o0Lkxm37Q==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -18857,12 +18847,12 @@
}
},
"node_modules/react-router": {
"version": "6.30.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
"version": "6.30.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz",
"integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.0"
"@remix-run/router": "1.23.1"
},
"engines": {
"node": ">=14.0.0"
@@ -18872,14 +18862,14 @@
}
},
"node_modules/react-router-dom": {
"version": "6.30.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
"version": "6.30.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz",
"integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@remix-run/router": "1.23.0",
"react-router": "6.30.1"
"@remix-run/router": "1.23.1",
"react-router": "6.30.2"
},
"engines": {
"node": ">=14.0.0"

View File

@@ -31,7 +31,7 @@
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-header": "^6.6.0",
"@edx/frontend-component-header": "^8.0.0",
"@edx/frontend-enterprise-hotjar": "7.2.0",
"@edx/frontend-platform": "^8.3.1",
"@edx/openedx-atlas": "^0.7.0",
@@ -44,8 +44,7 @@
"@redux-devtools/extension": "3.3.0",
"@reduxjs/toolkit": "^2.0.0",
"classnames": "^2.3.1",
"core-js": "3.46.0",
"filesize": "^10.0.0",
"core-js": "3.47.0",
"font-awesome": "4.7.0",
"history": "5.3.0",
"lodash": "^4.17.21",
@@ -56,7 +55,7 @@
"react-helmet": "^6.1.0",
"react-intl": "6.8.9",
"react-redux": "^7.2.4",
"react-router-dom": "6.30.1",
"react-router-dom": "6.30.2",
"react-share": "^4.4.0",
"redux": "4.2.1",
"redux-logger": "3.0.6",

View File

@@ -2,8 +2,6 @@ import React from 'react';
import { Helmet } from 'react-helmet';
import { useIntl } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import { initializeHotjar } from '@edx/frontend-enterprise-hotjar';
import { ErrorPage, AppContext } from '@edx/frontend-platform/react';
import { FooterSlot } from '@edx/frontend-component-footer';
@@ -59,17 +57,6 @@ export const App = () => {
window.actions = actions;
window.track = track;
}
if (getConfig().HOTJAR_APP_ID) {
try {
initializeHotjar({
hotjarId: getConfig().HOTJAR_APP_ID,
hotjarVersion: getConfig().HOTJAR_VERSION,
hotjarDebug: !!getConfig().HOTJAR_DEBUG,
});
} catch (error) {
logError(error);
}
}
}, [authenticatedUser, loadData]);
return (
<>

View File

@@ -1,25 +0,0 @@
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { logError, logInfo } from '@edx/frontend-platform/logging';
export const noticesUrl = `${getConfig().LMS_BASE_URL}/notices/api/v1/unacknowledged`;
export const getNotices = ({ onLoad, notFoundMessage }) => {
const authenticatedUser = getAuthenticatedUser();
const handleError = async (e) => {
// Error probably means that notices is not installed, which is fine.
const { customAttributes: { httpErrorStatus } } = e;
if (httpErrorStatus === 404) {
logInfo(`${e}. ${notFoundMessage}`);
} else {
logError(e);
}
};
if (authenticatedUser) {
return getAuthenticatedHttpClient().get(noticesUrl, {}).then(onLoad).catch(handleError);
}
return null;
};
export default { getNotices };

View File

@@ -1,65 +0,0 @@
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { logError, logInfo } from '@edx/frontend-platform/logging';
import * as api from './api';
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(() => ({
LMS_BASE_URL: 'test-lms-url',
})),
}));
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedHttpClient: jest.fn(),
getAuthenticatedUser: jest.fn(),
}));
jest.mock('@edx/frontend-platform/logging', () => ({
logError: jest.fn(),
logInfo: jest.fn(),
}));
const testData = 'test-data';
const successfulGet = () => Promise.resolve(testData);
const error404 = { customAttributes: { httpErrorStatus: 404 }, test: 'error' };
const error404Get = () => Promise.reject(error404);
const error500 = { customAttributes: { httpErrorStatus: 500 }, test: 'error' };
const error500Get = () => Promise.reject(error500);
const get = jest.fn().mockImplementation(successfulGet);
getAuthenticatedHttpClient.mockReturnValue({ get });
const authenticatedUser = { fake: 'user' };
getAuthenticatedUser.mockReturnValue(authenticatedUser);
const onLoad = jest.fn();
describe('getNotices api method', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('behavior', () => {
describe('not authenticated', () => {
it('does not fetch anything', () => {
getAuthenticatedUser.mockReturnValueOnce(null);
api.getNotices({ onLoad });
expect(get).not.toHaveBeenCalled();
});
});
describe('authenticated', () => {
it('fetches noticesUrl with onLoad behavior', async () => {
await api.getNotices({ onLoad });
expect(get).toHaveBeenCalledWith(api.noticesUrl, {});
expect(onLoad).toHaveBeenCalledWith(testData);
});
it('calls logInfo if fetch fails with 404', async () => {
get.mockImplementation(error404Get);
await api.getNotices({ onLoad });
expect(logInfo).toHaveBeenCalledWith(`${error404}. ${api.error404Message}`);
});
it('calls logError if fetch fails with non-404 error', async () => {
get.mockImplementation(error500Get);
await api.getNotices({ onLoad });
expect(logError).toHaveBeenCalledWith(error500);
});
});
});
});

View File

@@ -1,40 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { StrictDict } from 'utils';
import { getNotices } from './api';
import * as module from './hooks';
import messages from './messages';
/**
* This component uses the platform-plugin-notices plugin to function.
* If the user has an unacknowledged notice, they will be rerouted off
* course home and onto a full-screen notice page. If the plugin is not
* installed, or there are no notices, we just passthrough this component.
*/
export const state = StrictDict({
isRedirected: (val) => React.useState(val), // eslint-disable-line
});
export const useNoticesWrapperData = () => {
const [isRedirected, setIsRedirected] = module.state.isRedirected();
const { formatMessage } = useIntl();
React.useEffect(() => {
if (getConfig().ENABLE_NOTICES) {
getNotices({
onLoad: (data) => {
if (data?.data?.results?.length > 0) {
setIsRedirected(true);
window.location.replace(`${data.data.results[0]}?next=${window.location.href}`);
}
},
notFoundMessage: formatMessage(messages.error404Message),
});
}
}, [setIsRedirected, formatMessage]);
return { isRedirected };
};
export default useNoticesWrapperData;

View File

@@ -1,99 +0,0 @@
import React from 'react';
import { MockUseState, formatMessage } from 'testUtils';
import { getConfig } from '@edx/frontend-platform';
import { getNotices } from './api';
import * as hooks from './hooks';
jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn() }));
jest.mock('./api', () => ({ getNotices: jest.fn() }));
jest.mock('react', () => ({
...jest.requireActual('react'),
useEffect: jest.fn((cb, prereqs) => ({ useEffect: { cb, prereqs } })),
useContext: jest.fn(context => context),
}));
jest.mock('@edx/frontend-platform/i18n', () => {
const { formatMessage: fn } = jest.requireActual('testUtils');
return {
...jest.requireActual('@edx/frontend-platform/i18n'),
useIntl: () => ({
formatMessage: fn,
}),
};
});
getConfig.mockReturnValue({ ENABLE_NOTICES: true });
const state = new MockUseState(hooks);
let hook;
describe('NoticesWrapper hooks', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('state hooks', () => {
state.testGetter(state.keys.isRedirected);
});
describe('useNoticesWrapperData', () => {
beforeEach(() => {
state.mock();
});
describe('behavior', () => {
it('initializes state hooks', () => {
hooks.useNoticesWrapperData();
expect(hooks.state.isRedirected).toHaveBeenCalledWith();
});
describe('effects', () => {
it('does not call notices if not enabled', () => {
getConfig.mockReturnValueOnce({ ENABLE_NOTICES: false });
hooks.useNoticesWrapperData();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
cb();
expect(getNotices).not.toHaveBeenCalled();
});
describe('getNotices call (if enabled) onLoad behavior', () => {
it('does not redirect if there are no results', () => {
hooks.useNoticesWrapperData();
expect(React.useEffect).toHaveBeenCalled();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
cb();
expect(getNotices).toHaveBeenCalled();
const { onLoad } = getNotices.mock.calls[0][0];
onLoad({});
expect(state.setState.isRedirected).not.toHaveBeenCalled();
onLoad({ data: {} });
expect(state.setState.isRedirected).not.toHaveBeenCalled();
onLoad({ data: { results: [] } });
expect(state.setState.isRedirected).not.toHaveBeenCalled();
});
it('redirects and set isRedirected if results are returned', () => {
delete window.location;
window.location = { replace: jest.fn(), href: 'test-old-href' };
hooks.useNoticesWrapperData();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
cb();
expect(getNotices).toHaveBeenCalled();
const { onLoad } = getNotices.mock.calls[0][0];
const target = 'url-target';
onLoad({ data: { results: [target] } });
expect(state.setState.isRedirected).toHaveBeenCalledWith(true);
expect(window.location.replace).toHaveBeenCalledWith(
`${target}?next=${window.location.href}`,
);
});
});
});
});
describe('output', () => {
it('forwards isRedirected from state call', () => {
hook = hooks.useNoticesWrapperData();
expect(hook.isRedirected).toEqual(state.stateVals.isRedirected);
});
});
});
});

View File

@@ -1,25 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import useNoticesWrapperData from './hooks';
/**
* This component uses the platform-plugin-notices plugin to function.
* If the user has an unacknowledged notice, they will be rerouted off
* course home and onto a full-screen notice page. If the plugin is not
* installed, or there are no notices, we just passthrough this component.
*/
const NoticesWrapper = ({ children }) => {
const { isRedirected } = useNoticesWrapperData();
return (
<div>
{isRedirected === true ? null : children}
</div>
);
};
NoticesWrapper.propTypes = {
children: PropTypes.node.isRequired,
};
export default NoticesWrapper;

View File

@@ -1,36 +0,0 @@
import { render, screen } from '@testing-library/react';
import useNoticesWrapperData from './hooks';
import NoticesWrapper from '.';
jest.mock('./hooks', () => jest.fn());
const hookProps = { isRedirected: false };
const children = [<b key={1}>some</b>, <i key={2}>children</i>];
describe('NoticesWrapper component', () => {
beforeEach(() => {
useNoticesWrapperData.mockClear();
});
describe('behavior', () => {
it('initializes hooks', () => {
useNoticesWrapperData.mockReturnValue(hookProps);
render(<NoticesWrapper>{children}</NoticesWrapper>);
expect(useNoticesWrapperData).toHaveBeenCalledWith();
});
});
describe('output', () => {
it('does not show children if redirected', () => {
useNoticesWrapperData.mockReturnValueOnce({ isRedirected: true });
render(<NoticesWrapper>{children}</NoticesWrapper>);
expect(screen.queryByText('some')).not.toBeInTheDocument();
expect(screen.queryByText('children')).not.toBeInTheDocument();
});
it('shows children if not redirected', () => {
useNoticesWrapperData.mockReturnValue(hookProps);
render(<NoticesWrapper>{children}</NoticesWrapper>);
expect(screen.getByText('some')).toBeInTheDocument();
expect(screen.getByText('children')).toBeInTheDocument();
});
});
});

View File

@@ -1,11 +0,0 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
error404Message: {
id: 'learner-dash.notices.error404Message',
defaultMessage: 'This probably happened because the notices plugin is not installed on platform.',
description: 'Error message when notices API returns 404',
},
});
export default messages;

View File

@@ -14,7 +14,6 @@ const configuration = {
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN || '',
SUPPORT_URL: process.env.SUPPORT_URL || null,
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
CAREER_LINK_URL: process.env.CAREER_LINK_URL || null,
LOGO_URL: process.env.LOGO_URL,
ENABLE_EDX_PERSONAL_DASHBOARD: process.env.ENABLE_EDX_PERSONAL_DASHBOARD === 'true',

View File

@@ -27,7 +27,6 @@ import { configuration } from './config';
import messages from './i18n';
import App from './App';
import NoticesWrapper from './components/NoticesWrapper';
subscribe(APP_READY, () => {
const root = createRoot(document.getElementById('root'));
@@ -35,12 +34,10 @@ subscribe(APP_READY, () => {
root.render(
<StrictMode>
<AppProvider store={store}>
<NoticesWrapper>
<Routes>
<Route path="/" element={<PageWrap><App /></PageWrap>} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</NoticesWrapper>
<Routes>
<Route path="/" element={<PageWrap><App /></PageWrap>} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</AppProvider>
</StrictMode>,
);

View File

@@ -35,7 +35,6 @@ jest.mock('@edx/frontend-platform', () => ({
jest.mock('data/store', () => ({ redux: 'store' }));
jest.mock('./App', () => 'App');
jest.mock('components/NoticesWrapper', () => 'NoticesWrapper');
describe('app registry', () => {
let getElement;

View File

@@ -39,7 +39,6 @@ jest.unmock('reselect');
jest.unmock('hooks');
jest.mock('plugin-slots/WidgetSidebarSlot', () => jest.fn(() => 'widget-sidebar'));
jest.mock('components/NoticesWrapper', () => 'notices-wrapper');
jest.mock('@edx/frontend-platform', () => ({
...jest.requireActual('@edx/frontend-platform'),
@@ -55,10 +54,6 @@ jest.mock('@edx/frontend-platform/auth', () => ({
getLoginRedirectUrl: jest.fn(),
}));
jest.mock('@edx/frontend-enterprise-hotjar', () => ({
initializeHotjar: jest.fn(),
}));
jest.mock('@edx/frontend-platform/i18n', () => ({
...jest.requireActual('@edx/frontend-platform/i18n'),
useIntl: () => ({