From 5e0eef68e306566ed6e8d34beec2f2ef5c0fe673 Mon Sep 17 00:00:00 2001 From: Maxwell Frank Date: Wed, 28 Jun 2023 18:16:10 +0000 Subject: [PATCH] refactor: remove Skills Builder --- .env | 5 - .env.development | 5 - .env.test | 5 - package-lock.json | 353 ------------------ package.json | 3 - src/index.jsx | 6 - src/index.scss | 3 - src/routes/AppRoutes.jsx | 5 - src/routes/routes.test.jsx | 18 - src/skills-builder/SkillsBuilder.jsx | 11 - src/skills-builder/data/actions.js | 26 -- src/skills-builder/data/constants.js | 9 - src/skills-builder/data/reducer.js | 41 -- src/skills-builder/data/test/reducer.test.js | 60 --- .../images/card-imagecap-fallback.png | Bin 16761 -> 0 bytes src/skills-builder/images/edX-logo.svg | 3 - src/skills-builder/images/headerImage.png | Bin 13433 -> 0 bytes src/skills-builder/index.js | 2 - .../SkillsBuilderProvider.jsx | 32 -- .../skills-builder-context/index.js | 2 - .../SkillsBuilderHeader.jsx | 39 -- .../skills-builder-header/index.js | 2 - .../skills-builder-header/messages.js | 21 -- .../skillsBuilderHeader.scss | 4 - .../SkillsBuilderModal.jsx | 114 ------ .../skills-builder-modal/index.js | 2 - .../skills-builder-modal/messages.js | 32 -- .../select-preferences/CareerInterestCard.jsx | 51 --- .../CareerInterestSelect.jsx | 65 ---- .../select-preferences/GoalSelect.jsx | 56 --- .../JobTitleInstantSearch.jsx | 43 --- .../select-preferences/JobTitleSelect.jsx | 79 ---- .../select-preferences/SelectPreferences.jsx | 38 -- .../select-preferences/index.js | 2 - .../select-preferences/messages.js | 80 ---- .../test/SelectPreferences.test.jsx | 180 --------- .../skillsBuilderModal.scss | 22 -- .../view-results/CarouselStack.jsx | 79 ---- .../view-results/RecommendationCard.jsx | 62 --- .../RelatedSkillsSelectableBoxSet.jsx | 57 --- .../view-results/ViewResults.jsx | 145 ------- .../view-results/data/constants.js | 14 - .../view-results/data/hooks.js | 39 -- .../view-results/data/service.js | 34 -- .../view-results/data/test/hooks.test.jsx | 46 --- .../view-results/index.js | 2 - .../view-results/messages.js | 31 -- .../view-results/test/ViewResults.test.jsx | 179 --------- .../test/SkillsBuilder.test.jsx | 23 -- .../test/__mocks__/jobSkills.mockData.js | 69 ---- .../test/setupSkillsBuilder.jsx | 55 --- src/skills-builder/utils/search.js | 110 ------ src/skills-builder/utils/tests/search.test.js | 74 ---- 53 files changed, 2438 deletions(-) delete mode 100644 src/skills-builder/SkillsBuilder.jsx delete mode 100644 src/skills-builder/data/actions.js delete mode 100644 src/skills-builder/data/constants.js delete mode 100644 src/skills-builder/data/reducer.js delete mode 100644 src/skills-builder/data/test/reducer.test.js delete mode 100644 src/skills-builder/images/card-imagecap-fallback.png delete mode 100644 src/skills-builder/images/edX-logo.svg delete mode 100644 src/skills-builder/images/headerImage.png delete mode 100644 src/skills-builder/index.js delete mode 100644 src/skills-builder/skills-builder-context/SkillsBuilderProvider.jsx delete mode 100644 src/skills-builder/skills-builder-context/index.js delete mode 100644 src/skills-builder/skills-builder-header/SkillsBuilderHeader.jsx delete mode 100644 src/skills-builder/skills-builder-header/index.js delete mode 100644 src/skills-builder/skills-builder-header/messages.js delete mode 100644 src/skills-builder/skills-builder-header/skillsBuilderHeader.scss delete mode 100644 src/skills-builder/skills-builder-modal/SkillsBuilderModal.jsx delete mode 100644 src/skills-builder/skills-builder-modal/index.js delete mode 100644 src/skills-builder/skills-builder-modal/messages.js delete mode 100644 src/skills-builder/skills-builder-modal/select-preferences/CareerInterestCard.jsx delete mode 100644 src/skills-builder/skills-builder-modal/select-preferences/CareerInterestSelect.jsx delete mode 100644 src/skills-builder/skills-builder-modal/select-preferences/GoalSelect.jsx delete mode 100644 src/skills-builder/skills-builder-modal/select-preferences/JobTitleInstantSearch.jsx delete mode 100644 src/skills-builder/skills-builder-modal/select-preferences/JobTitleSelect.jsx delete mode 100644 src/skills-builder/skills-builder-modal/select-preferences/SelectPreferences.jsx delete mode 100644 src/skills-builder/skills-builder-modal/select-preferences/index.js delete mode 100644 src/skills-builder/skills-builder-modal/select-preferences/messages.js delete mode 100644 src/skills-builder/skills-builder-modal/select-preferences/test/SelectPreferences.test.jsx delete mode 100644 src/skills-builder/skills-builder-modal/skillsBuilderModal.scss delete mode 100644 src/skills-builder/skills-builder-modal/view-results/CarouselStack.jsx delete mode 100644 src/skills-builder/skills-builder-modal/view-results/RecommendationCard.jsx delete mode 100644 src/skills-builder/skills-builder-modal/view-results/RelatedSkillsSelectableBoxSet.jsx delete mode 100644 src/skills-builder/skills-builder-modal/view-results/ViewResults.jsx delete mode 100644 src/skills-builder/skills-builder-modal/view-results/data/constants.js delete mode 100644 src/skills-builder/skills-builder-modal/view-results/data/hooks.js delete mode 100644 src/skills-builder/skills-builder-modal/view-results/data/service.js delete mode 100644 src/skills-builder/skills-builder-modal/view-results/data/test/hooks.test.jsx delete mode 100644 src/skills-builder/skills-builder-modal/view-results/index.js delete mode 100644 src/skills-builder/skills-builder-modal/view-results/messages.js delete mode 100644 src/skills-builder/skills-builder-modal/view-results/test/ViewResults.test.jsx delete mode 100644 src/skills-builder/test/SkillsBuilder.test.jsx delete mode 100644 src/skills-builder/test/__mocks__/jobSkills.mockData.js delete mode 100644 src/skills-builder/test/setupSkillsBuilder.jsx delete mode 100644 src/skills-builder/utils/search.js delete mode 100644 src/skills-builder/utils/tests/search.test.js diff --git a/.env b/.env index 3521dee..52ce6a0 100644 --- a/.env +++ b/.env @@ -26,9 +26,4 @@ COLLECT_YEAR_OF_BIRTH=true APP_ID='' MFE_CONFIG_API_URL='' SEARCH_CATALOG_URL='' -ENABLE_SKILLS_BUILDER='' ENABLE_SKILLS_BUILDER_PROFILE='' -ALGOLIA_APP_ID='' -ALGOLIA_JOBS_INDEX_NAME='' -ALGOLIA_PRODUCT_INDEX_NAME='' -ALGOLIA_SEARCH_API_KEY='' diff --git a/.env.development b/.env.development index e0b32bf..6b42cba 100644 --- a/.env.development +++ b/.env.development @@ -27,9 +27,4 @@ COLLECT_YEAR_OF_BIRTH=true APP_ID='' MFE_CONFIG_API_URL='' SEARCH_CATALOG_URL='http://localhost:18000/courses' -ENABLE_SKILLS_BUILDER='true' ENABLE_SKILLS_BUILDER_PROFILE='' -ALGOLIA_APP_ID='' -ALGOLIA_JOBS_INDEX_NAME='' -ALGOLIA_PRODUCT_INDEX_NAME='' -ALGOLIA_SEARCH_API_KEY='' diff --git a/.env.test b/.env.test index 0b6574a..b79d85c 100644 --- a/.env.test +++ b/.env.test @@ -18,13 +18,8 @@ LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico ENABLE_LEARNER_RECORD_MFE='' -ENABLE_SKILLS_BUILDER='true' ENABLE_SKILLS_BUILDER_PROFILE='' LEARNER_RECORD_MFE_BASE_URL='http://localhost:1990' COLLECT_YEAR_OF_BIRTH=true APP_ID='' MFE_CONFIG_API_URL='' -ALGOLIA_APP_ID='' -ALGOLIA_JOBS_INDEX_NAME='' -ALGOLIA_PRODUCT_INDEX_NAME='' -ALGOLIA_SEARCH_API_KEY='' diff --git a/package-lock.json b/package-lock.json index fbccca8..b0bda80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/react-fontawesome": "0.2.0", - "algoliasearch": "4.18.0", "classnames": "2.3.2", "core-js": "3.31.0", "history": "4.10.1", @@ -31,7 +30,6 @@ "react": "16.14.0", "react-dom": "16.14.0", "react-helmet": "6.1.0", - "react-instantsearch-hooks-web": "^6.45.0", "react-redux": "7.2.9", "react-router": "5.3.4", "react-router-dom": "5.3.4", @@ -51,7 +49,6 @@ "@edx/frontend-build": "12.8.57", "@edx/reactifex": "2.2.0", "@testing-library/react": "11.2.7", - "@testing-library/react-hooks": "^8.0.1", "codecov": "3.8.3", "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.7", @@ -61,140 +58,6 @@ "redux-mock-store": "1.5.4" } }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.18.0.tgz", - "integrity": "sha512-rUAs49NLlO8LVLgGzM4cLkw8NJLKguQLgvFmBEe3DyzlinoqxzQMHfKZs6TSq4LZfw/z8qHvRo8NcTAAUJQLcw==", - "dependencies": { - "@algolia/cache-common": "4.18.0" - } - }, - "node_modules/@algolia/cache-common": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.18.0.tgz", - "integrity": "sha512-BmxsicMR4doGbeEXQu8yqiGmiyvpNvejYJtQ7rvzttEAMxOPoWEHrWyzBQw4x7LrBY9pMrgv4ZlUaF8PGzewHg==" - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.18.0.tgz", - "integrity": "sha512-evD4dA1nd5HbFdufBxLqlJoob7E2ozlqJZuV3YlirNx5Na4q1LckIuzjNYZs2ddLzuTc/Xd5O3Ibf7OwPskHxw==", - "dependencies": { - "@algolia/cache-common": "4.18.0" - } - }, - "node_modules/@algolia/client-account": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.18.0.tgz", - "integrity": "sha512-XsDnlROr3+Z1yjxBJjUMfMazi1V155kVdte6496atvBgOEtwCzTs3A+qdhfsAnGUvaYfBrBkL0ThnhMIBCGcew==", - "dependencies": { - "@algolia/client-common": "4.18.0", - "@algolia/client-search": "4.18.0", - "@algolia/transporter": "4.18.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.18.0.tgz", - "integrity": "sha512-chEUSN4ReqU7uRQ1C8kDm0EiPE+eJeAXiWcBwLhEynfNuTfawN9P93rSZktj7gmExz0C8XmkbBU19IQ05wCNrQ==", - "dependencies": { - "@algolia/client-common": "4.18.0", - "@algolia/client-search": "4.18.0", - "@algolia/requester-common": "4.18.0", - "@algolia/transporter": "4.18.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.18.0.tgz", - "integrity": "sha512-7N+soJFP4wn8tjTr3MSUT/U+4xVXbz4jmeRfWfVAzdAbxLAQbHa0o/POSdTvQ8/02DjCLelloZ1bb4ZFVKg7Wg==", - "dependencies": { - "@algolia/requester-common": "4.18.0", - "@algolia/transporter": "4.18.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.18.0.tgz", - "integrity": "sha512-+PeCjODbxtamHcPl+couXMeHEefpUpr7IHftj4Y4Nia1hj8gGq4VlIcqhToAw8YjLeCTfOR7r7xtj3pJcYdP8A==", - "dependencies": { - "@algolia/client-common": "4.18.0", - "@algolia/requester-common": "4.18.0", - "@algolia/transporter": "4.18.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.18.0.tgz", - "integrity": "sha512-F9xzQXTjm6UuZtnsLIew6KSraXQ0AzS/Ee+OD+mQbtcA/K1sg89tqb8TkwjtiYZ0oij13u3EapB3gPZwm+1Y6g==", - "dependencies": { - "@algolia/client-common": "4.18.0", - "@algolia/requester-common": "4.18.0", - "@algolia/transporter": "4.18.0" - } - }, - "node_modules/@algolia/events": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", - "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" - }, - "node_modules/@algolia/logger-common": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.18.0.tgz", - "integrity": "sha512-46etYgSlkoKepkMSyaoriSn2JDgcrpc/nkOgou/lm0y17GuMl9oYZxwKKTSviLKI5Irk9nSKGwnBTQYwXOYdRg==" - }, - "node_modules/@algolia/logger-console": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.18.0.tgz", - "integrity": "sha512-3P3VUYMl9CyJbi/UU1uUNlf6Z8N2ltW3Oqhq/nR7vH0CjWv32YROq3iGWGxB2xt3aXobdUPXs6P0tHSKRmNA6g==", - "dependencies": { - "@algolia/logger-common": "4.18.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.18.0.tgz", - "integrity": "sha512-/AcWHOBub2U4TE/bPi4Gz1XfuLK6/7dj4HJG+Z2SfQoS1RjNLshZclU3OoKIkFp8D2NC7+BNsPvr9cPLyW8nyQ==", - "dependencies": { - "@algolia/requester-common": "4.18.0" - } - }, - "node_modules/@algolia/requester-common": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.18.0.tgz", - "integrity": "sha512-xlT8R1qYNRBCi1IYLsx7uhftzdfsLPDGudeQs+xvYB4sQ3ya7+ppolB/8m/a4F2gCkEO6oxpp5AGemM7kD27jA==" - }, - "node_modules/@algolia/requester-node-http": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.18.0.tgz", - "integrity": "sha512-TGfwj9aeTVgOUhn5XrqBhwUhUUDnGIKlI0kCBMdR58XfXcfdwomka+CPIgThRbfYw04oQr31A6/95ZH2QVJ9UQ==", - "dependencies": { - "@algolia/requester-common": "4.18.0" - } - }, - "node_modules/@algolia/transporter": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.18.0.tgz", - "integrity": "sha512-xbw3YRUGtXQNG1geYFEDDuFLZt4Z8YNKbamHPkzr3rWc6qp4/BqEeXcI2u/P/oMq2yxtXgMxrCxOPA8lyIe5jw==", - "dependencies": { - "@algolia/cache-common": "4.18.0", - "@algolia/logger-common": "4.18.0", - "@algolia/requester-common": "4.18.0" - } - }, - "node_modules/@algolia/ui-components-highlight-vdom": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@algolia/ui-components-highlight-vdom/-/ui-components-highlight-vdom-1.2.1.tgz", - "integrity": "sha512-IlYgIaCUEkz9ezNbwugwKv991oOHhveyq6nzL0F1jDzg1p3q5Yj/vO4KpNG910r2dwGCG3nEm5GtChcLnarhFA==", - "dependencies": { - "@algolia/ui-components-shared": "1.2.1", - "@babel/runtime": "^7.0.0" - } - }, - "node_modules/@algolia/ui-components-shared": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@algolia/ui-components-shared/-/ui-components-shared-1.2.1.tgz", - "integrity": "sha512-a7mYHf/GVQfhAx/HRiMveKkFvHspQv/REdG+C/FIOosiSmNZxX7QebDwJkrGSmDWdXO12D0Qv1xn3AytFcEDlQ==" - }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -4380,36 +4243,6 @@ "react-dom": "*" } }, - "node_modules/@testing-library/react-hooks": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", - "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "react-error-boundary": "^3.1.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0", - "react": "^16.9.0 || ^17.0.0", - "react-dom": "^16.9.0 || ^17.0.0", - "react-test-renderer": "^16.9.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-test-renderer": { - "optional": true - } - } - }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -4532,11 +4365,6 @@ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" }, - "node_modules/@types/dom-speech-recognition": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@types/dom-speech-recognition/-/dom-speech-recognition-0.0.1.tgz", - "integrity": "sha512-udCxb8DvjcDKfk1WTBzDsxFbLgYxmQGKrE/ricoMqHRNjSlSUCcamVTA5lIQqzY10mY5qCY0QDwBfFEwhfoDPw==" - }, "node_modules/@types/eslint": { "version": "8.37.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", @@ -4598,11 +4426,6 @@ "@types/node": "*" } }, - "node_modules/@types/google.maps": { - "version": "3.52.6", - "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.52.6.tgz", - "integrity": "sha512-CnwN5UcezNiRuJzV0wGIsqXWNwMM6WzIxmy9lOUx+yauRQMee5XH/N7NaVOsiY5T5ygrnBwPO+csND652HYsTQ==" - }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -4611,11 +4434,6 @@ "@types/node": "*" } }, - "node_modules/@types/hogan.js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/hogan.js/-/hogan.js-3.0.1.tgz", - "integrity": "sha512-D03i/2OY7kGyMq9wdQ7oD8roE49z/ZCZThe/nbahtvuqCNZY9T2MfedOWyeBdbEpY2W8Gnh/dyJLdFtUCOkYbg==" - }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -5087,11 +4905,6 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -5255,38 +5068,6 @@ "ajv": "^8.8.2" } }, - "node_modules/algoliasearch": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.18.0.tgz", - "integrity": "sha512-pCuVxC1SVcpc08ENH32T4sLKSyzoU7TkRIDBMwSLfIiW+fq4znOmWDkAygHZ6pRcO9I1UJdqlfgnV7TRj+MXrA==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.18.0", - "@algolia/cache-common": "4.18.0", - "@algolia/cache-in-memory": "4.18.0", - "@algolia/client-account": "4.18.0", - "@algolia/client-analytics": "4.18.0", - "@algolia/client-common": "4.18.0", - "@algolia/client-personalization": "4.18.0", - "@algolia/client-search": "4.18.0", - "@algolia/logger-common": "4.18.0", - "@algolia/logger-console": "4.18.0", - "@algolia/requester-browser-xhr": "4.18.0", - "@algolia/requester-common": "4.18.0", - "@algolia/requester-node-http": "4.18.0", - "@algolia/transporter": "4.18.0" - } - }, - "node_modules/algoliasearch-helper": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.13.2.tgz", - "integrity": "sha512-1bZjtHuqCBYw7Eu3Qh0Jfq4s63UcbOs6VvLPdt7kxn5+zMgs46xiXgc65YhZBNM3hDGrudhAX9hDhE9OP+rKUw==", - "dependencies": { - "@algolia/events": "^4.0.1" - }, - "peerDependencies": { - "algoliasearch": ">= 3.1 < 6" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -10242,18 +10023,6 @@ "value-equal": "^1.0.1" } }, - "node_modules/hogan.js": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz", - "integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==", - "dependencies": { - "mkdirp": "0.3.0", - "nopt": "1.0.10" - }, - "bin": { - "hulk": "bin/hulk" - } - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -10335,11 +10104,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/htm": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", - "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==" - }, "node_modules/html-element-map": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.3.1.tgz", @@ -10780,29 +10544,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, - "node_modules/instantsearch.js": { - "version": "4.56.3", - "resolved": "https://registry.npmjs.org/instantsearch.js/-/instantsearch.js-4.56.3.tgz", - "integrity": "sha512-ImMIMaqLL+n0PU3aQWaiX6TFg9h13ys4AY/NcZ4Ew7OVw20dzHYQeyCDZVdDJ52BHrO53fNZ7+omnG5ZF6v6xg==", - "dependencies": { - "@algolia/events": "^4.0.1", - "@algolia/ui-components-highlight-vdom": "^1.2.1", - "@algolia/ui-components-shared": "^1.2.1", - "@types/dom-speech-recognition": "^0.0.1", - "@types/google.maps": "^3.45.3", - "@types/hogan.js": "^3.0.0", - "@types/qs": "^6.5.3", - "algoliasearch-helper": "^3.13.2", - "hogan.js": "^3.0.2", - "htm": "^3.0.0", - "preact": "^10.10.0", - "qs": "^6.5.1 < 6.10", - "search-insights": "^2.6.0" - }, - "peerDependencies": { - "algoliasearch": ">= 3.1 < 6" - } - }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -13169,15 +12910,6 @@ "node": ">=0.10.0" } }, - "node_modules/mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "engines": { - "node": "*" - } - }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -13416,20 +13148,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -14751,15 +14469,6 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, - "node_modules/preact": { - "version": "10.13.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.2.tgz", - "integrity": "sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -15261,22 +14970,6 @@ "react": ">= 16.8 || 18.0.0" } }, - "node_modules/react-error-boundary": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", - "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - }, - "peerDependencies": { - "react": ">=16.13.1" - } - }, "node_modules/react-error-overlay": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", @@ -15349,36 +15042,6 @@ "react": ">=16.3.0" } }, - "node_modules/react-instantsearch-hooks": { - "version": "6.45.0", - "resolved": "https://registry.npmjs.org/react-instantsearch-hooks/-/react-instantsearch-hooks-6.45.0.tgz", - "integrity": "sha512-SjE3lhWA+1saajDJ1YULQ1n+Fo0g3lWhnKMAXYrF0p5COKeCbJns6CmIH2t42H3hoTcmQz0Qm0WDfLaRAjssPQ==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "algoliasearch-helper": "^3.13.2", - "instantsearch.js": "4.56.3", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "algoliasearch": ">= 3.1 < 5", - "react": ">= 16.8.0 < 19" - } - }, - "node_modules/react-instantsearch-hooks-web": { - "version": "6.45.0", - "resolved": "https://registry.npmjs.org/react-instantsearch-hooks-web/-/react-instantsearch-hooks-web-6.45.0.tgz", - "integrity": "sha512-/VOnDoQbyL4rY8tYtnnr8vPKKTyTupZQ+K26GWspid1izlDY7KLWZvtvcdePTrv3DU5bQhSxgD6/3WUmUtiYQQ==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "instantsearch.js": "4.56.3", - "react-instantsearch-hooks": "6.45.0" - }, - "peerDependencies": { - "algoliasearch": ">= 3.1 < 5", - "react": ">= 16.8.0 < 19", - "react-dom": ">= 16.8.0 < 19" - } - }, "node_modules/react-intl": { "version": "5.25.1", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.25.1.tgz", @@ -16787,14 +16450,6 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "node_modules/search-insights": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.6.0.tgz", - "integrity": "sha512-vU2/fJ+h/Mkm/DJOe+EaM5cafJv/1rRTZpGJTuFPf/Q5LjzgMDsqPdSaZsAe+GAWHHsfsu+rQSAn6c8IGtBEVw==", - "engines": { - "node": ">=8.16.0" - } - }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -18892,14 +18547,6 @@ } } }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index cec2090..a072411 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/react-fontawesome": "0.2.0", - "algoliasearch": "4.18.0", "classnames": "2.3.2", "core-js": "3.31.0", "history": "4.10.1", @@ -49,7 +48,6 @@ "react": "16.14.0", "react-dom": "16.14.0", "react-helmet": "6.1.0", - "react-instantsearch-hooks-web": "^6.40.1", "react-redux": "7.2.9", "react-router": "5.3.4", "react-router-dom": "5.3.4", @@ -69,7 +67,6 @@ "@edx/frontend-build": "12.8.57", "@edx/reactifex": "2.2.0", "@testing-library/react": "11.2.7", - "@testing-library/react-hooks": "^8.0.1", "codecov": "3.8.3", "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.7", diff --git a/src/index.jsx b/src/index.jsx index 1a38ded..8a6bde8 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -52,13 +52,7 @@ initialize({ config: () => { mergeConfig({ COLLECT_YEAR_OF_BIRTH: process.env.COLLECT_YEAR_OF_BIRTH, - ENABLE_SKILLS_BUILDER: process.env.ENABLE_SKILLS_BUILDER, ENABLE_SKILLS_BUILDER_PROFILE: process.env.ENABLE_SKILLS_BUILDER_PROFILE, - ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || null, - ALGOLIA_JOBS_INDEX_NAME: process.env.ALGOLIA_JOBS_INDEX_NAME || null, - ALGOLIA_PRODUCT_INDEX_NAME: process.env.ALGOLIA_PRODUCT_INDEX_NAME || null, - ALGOLIA_SEARCH_API_KEY: process.env.ALGOLIA_SEARCH_API_KEY || null, - MARKETING_SITE_SEARCH_URL: process.env.SEARCH_CATALOG_URL || null, }, 'App loadConfig override handler'); }, }, diff --git a/src/index.scss b/src/index.scss index 7b80a2b..173e0ec 100755 --- a/src/index.scss +++ b/src/index.scss @@ -6,6 +6,3 @@ @import "~@edx/frontend-component-footer/dist/footer"; @import './profile/index'; - -@import './skills-builder/skills-builder-modal/skillsBuilderModal.scss'; -@import './skills-builder/skills-builder-header/skillsBuilderHeader.scss'; diff --git a/src/routes/AppRoutes.jsx b/src/routes/AppRoutes.jsx index 8d202ef..097e359 100644 --- a/src/routes/AppRoutes.jsx +++ b/src/routes/AppRoutes.jsx @@ -1,18 +1,13 @@ import React from 'react'; -import { getConfig } from '@edx/frontend-platform'; import { AuthenticatedPageRoute, PageRoute, } from '@edx/frontend-platform/react'; import { Switch } from 'react-router-dom'; import { ProfilePage, NotFoundPage } from '../profile'; -import { SkillsBuilder } from '../skills-builder'; const AppRoutes = () => ( - {getConfig().ENABLE_SKILLS_BUILDER && ( - - )} diff --git a/src/routes/routes.test.jsx b/src/routes/routes.test.jsx index c344814..daa0ffa 100644 --- a/src/routes/routes.test.jsx +++ b/src/routes/routes.test.jsx @@ -13,21 +13,11 @@ jest.mock('@edx/frontend-platform/auth', () => ({ getLoginRedirectUrl: jest.fn(), })); -jest.mock('@edx/frontend-platform', () => ({ - getConfig: jest.fn(() => ({ - ENABLE_SKILLS_BUILDER: true, - })), -})); - jest.mock('../profile', () => ({ ProfilePage: () => (
Profile page
), NotFoundPage: () => (
Not found page
), })); -jest.mock('../skills-builder', () => ({ - SkillsBuilder: () => (
Skills Builder
), -})); - const RoutesWithProvider = (context, history) => ( @@ -73,14 +63,6 @@ describe('routes', () => { expect(screen.getByText('Profile page')).toBeTruthy(); }); - test('Skills Builder page should be accessible to unauthenticated users', () => { - history.push('/skills'); - render( - RoutesWithProvider(unauthenticatedUser, history), - ); - expect(screen.getByText('Skills Builder')).toBeTruthy(); - }); - test('should show NotFound page for a bad route', () => { history.push('/nonMatchingRoute'); render( diff --git a/src/skills-builder/SkillsBuilder.jsx b/src/skills-builder/SkillsBuilder.jsx deleted file mode 100644 index 0ebc3a5..0000000 --- a/src/skills-builder/SkillsBuilder.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { SkillsBuilderModal } from './skills-builder-modal'; -import { SkillsBuilderProvider } from './skills-builder-context'; - -const SkillsBuilder = () => ( - - - -); - -export default SkillsBuilder; diff --git a/src/skills-builder/data/actions.js b/src/skills-builder/data/actions.js deleted file mode 100644 index 70839e5..0000000 --- a/src/skills-builder/data/actions.js +++ /dev/null @@ -1,26 +0,0 @@ -import { - SET_GOAL, - SET_CURRENT_JOB_TITLE, - ADD_CAREER_INTEREST, - REMOVE_CAREER_INTEREST, -} from './constants'; - -export const setGoal = (payload) => ({ - type: SET_GOAL, - payload, -}); - -export const setCurrentJobTitle = (payload) => ({ - type: SET_CURRENT_JOB_TITLE, - payload, -}); - -export const addCareerInterest = (payload) => ({ - type: ADD_CAREER_INTEREST, - payload, -}); - -export const removeCareerInterest = (payload) => ({ - type: REMOVE_CAREER_INTEREST, - payload, -}); diff --git a/src/skills-builder/data/constants.js b/src/skills-builder/data/constants.js deleted file mode 100644 index 9bfe1ff..0000000 --- a/src/skills-builder/data/constants.js +++ /dev/null @@ -1,9 +0,0 @@ -// Actions for Skills Context -export const SET_GOAL = 'SET_GOAL'; -export const SET_CURRENT_JOB_TITLE = 'SET_CURRENT_JOB_TITLE'; -export const ADD_CAREER_INTEREST = 'ADD_CAREER_INTEREST'; -export const REMOVE_CAREER_INTEREST = 'REMOVE_CAREER_INTEREST'; - -// Stepper keys -export const STEP1 = 'select-your-preferences'; -export const STEP2 = 'review-your-results'; diff --git a/src/skills-builder/data/reducer.js b/src/skills-builder/data/reducer.js deleted file mode 100644 index e8876e0..0000000 --- a/src/skills-builder/data/reducer.js +++ /dev/null @@ -1,41 +0,0 @@ -import { - SET_GOAL, - SET_CURRENT_JOB_TITLE, - ADD_CAREER_INTEREST, - REMOVE_CAREER_INTEREST, -} from './constants'; - -export function skillsReducer(state, action) { - switch (action.type) { - case SET_GOAL: - return { - ...state, - currentGoal: action.payload, - }; - case SET_CURRENT_JOB_TITLE: - return { - ...state, - currentJobTitle: action.payload, - }; - case ADD_CAREER_INTEREST: - return { - ...state, - careerInterests: [...state.careerInterests, action.payload], - }; - case REMOVE_CAREER_INTEREST: - return { - ...state, - careerInterests: state.careerInterests.filter(interest => interest !== action.payload), - }; - default: - return state; - } -} - -export const skillsInitialState = { - currentGoal: '', - currentJobTitle: '', - careerInterests: [], -}; - -export default skillsReducer; diff --git a/src/skills-builder/data/test/reducer.test.js b/src/skills-builder/data/test/reducer.test.js deleted file mode 100644 index 07b11dd..0000000 --- a/src/skills-builder/data/test/reducer.test.js +++ /dev/null @@ -1,60 +0,0 @@ -import { skillsReducer, skillsInitialState } from '../reducer'; -import { - SET_GOAL, - SET_CURRENT_JOB_TITLE, - ADD_CAREER_INTEREST, - REMOVE_CAREER_INTEREST, -} from '../constants'; - -describe('skillsReducer', () => { - const testState = skillsInitialState; - beforeEach(() => jest.resetModules()); - - it('does not remove present data when SET_GOAL action is dispatched', () => { - const newGoalPayload = 'test-goal'; - const returnedState = skillsReducer(testState, { type: SET_GOAL, payload: newGoalPayload }); - const finalState = { - ...testState, - currentGoal: 'test-goal', - }; - expect(returnedState).toEqual(finalState); - }); - - it('does not remove present data when SET_JOB_TITLE action is dispatched', () => { - const newJobTitlePayload = 'test-job-title'; - const returnedState = skillsReducer(testState, { type: SET_CURRENT_JOB_TITLE, payload: newJobTitlePayload }); - const finalState = { - ...testState, - currentJobTitle: 'test-job-title', - }; - expect(returnedState).toEqual(finalState); - }); - - it('adds a careerInterest when ADD_CAREER_INTEREST action is dispatched', () => { - const newCareerInterestPayload = 'test-career-interest'; - const returnedState = skillsReducer(testState, { type: ADD_CAREER_INTEREST, payload: newCareerInterestPayload }); - const finalState = { - ...testState, - careerInterests: [...testState.careerInterests, 'test-career-interest'], - }; - expect(returnedState).toEqual(finalState); - }); - - it('removes a careerInterest when REMOVE_CAREER_INTEREST action is dispatched', () => { - const newCareerInterestPayload = 'test-career-interest'; - const testStateWithInterest = { - ...testState, - careerInterests: [newCareerInterestPayload], - }; - const returnedState = skillsReducer( - testStateWithInterest, - { type: REMOVE_CAREER_INTEREST, payload: newCareerInterestPayload }, - ); - const finalState = { - ...testStateWithInterest, - // override the 'careerInterests` field and remove 'test-career-interest' from the array - careerInterests: testStateWithInterest.careerInterests.filter(interest => interest !== newCareerInterestPayload), - }; - expect(returnedState).toEqual(finalState); - }); -}); diff --git a/src/skills-builder/images/card-imagecap-fallback.png b/src/skills-builder/images/card-imagecap-fallback.png deleted file mode 100644 index 1b031024472fa880abb59a94c8666fbf8135e9c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16761 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 ghg(29h*%xz{{R30 diff --git a/src/skills-builder/images/edX-logo.svg b/src/skills-builder/images/edX-logo.svg deleted file mode 100644 index 400cff1..0000000 --- a/src/skills-builder/images/edX-logo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/skills-builder/images/headerImage.png b/src/skills-builder/images/headerImage.png deleted file mode 100644 index 9df8987cba7f9944b67a0993f29f6bd99b3b2cf7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13433 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 bj$e?B*O8)(BQO5_V-W}{qa { - const [state, dispatch] = useReducer(reducer, skillsInitialState); - - const [searchClient, productSearchIndex, jobSearchIndex] = useAlgoliaSearch(); - - const value = useMemo(() => ({ - state, - dispatch, - algolia: { - searchClient, - productSearchIndex, - jobSearchIndex, - }, - }), [state, searchClient, productSearchIndex, jobSearchIndex]); - - return ( - - {children} - - ); -}; - -SkillsBuilderProvider.propTypes = { - children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired, -}; diff --git a/src/skills-builder/skills-builder-context/index.js b/src/skills-builder/skills-builder-context/index.js deleted file mode 100644 index 5ff6eb3..0000000 --- a/src/skills-builder/skills-builder-context/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export { SkillsBuilderProvider, SkillsBuilderContext } from './SkillsBuilderProvider'; diff --git a/src/skills-builder/skills-builder-header/SkillsBuilderHeader.jsx b/src/skills-builder/skills-builder-header/SkillsBuilderHeader.jsx deleted file mode 100644 index 17544c0..0000000 --- a/src/skills-builder/skills-builder-header/SkillsBuilderHeader.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import edXLogo from '../images/edX-logo.svg'; -import messages from './messages'; - -const SkillsBuilderHeader = ({ isMedium }) => { - const { formatMessage } = useIntl(); - - if (isMedium) { - return ( -
-

- {formatMessage(messages.skillsBuilderHeaderTitleIsMedium)} -

-
- ); - } - return ( -
- edx-logo -
-
-

- {formatMessage(messages.skillsBuilderHeaderTitle)} -

-

- {formatMessage(messages.skillsBuilderHeaderSubheading)} -

-
-
- ); -}; - -SkillsBuilderHeader.propTypes = { - isMedium: PropTypes.bool.isRequired, -}; - -export default SkillsBuilderHeader; diff --git a/src/skills-builder/skills-builder-header/index.js b/src/skills-builder/skills-builder-header/index.js deleted file mode 100644 index 00e6b02..0000000 --- a/src/skills-builder/skills-builder-header/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export { default as SkillsBuilderHeader } from './SkillsBuilderHeader'; diff --git a/src/skills-builder/skills-builder-header/messages.js b/src/skills-builder/skills-builder-header/messages.js deleted file mode 100644 index a39bcb2..0000000 --- a/src/skills-builder/skills-builder-header/messages.js +++ /dev/null @@ -1,21 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - skillsBuilderHeaderTitle: { - id: 'skills.builder.header.title', - defaultMessage: 'Skills Builder', - description: 'Title for the Skills Builder feature', - }, - skillsBuilderHeaderSubheading: { - id: 'skills.builder.header.subheading', - defaultMessage: 'Let edX be your guide', - description: 'Subheading to the Skills Builder title in the header component', - }, - skillsBuilderHeaderTitleIsMedium: { - id: 'skills.builder.header.title.is.medium', - defaultMessage: 'edX Skills builder', - description: 'Title for the Skills Builder feature when screen size is medium or less', - }, -}); - -export default messages; diff --git a/src/skills-builder/skills-builder-header/skillsBuilderHeader.scss b/src/skills-builder/skills-builder-header/skillsBuilderHeader.scss deleted file mode 100644 index ca4be48..0000000 --- a/src/skills-builder/skills-builder-header/skillsBuilderHeader.scss +++ /dev/null @@ -1,4 +0,0 @@ -.vertical-line { - border-left: 7px solid #D23228; - transform: rotate(13deg); -} diff --git a/src/skills-builder/skills-builder-modal/SkillsBuilderModal.jsx b/src/skills-builder/skills-builder-modal/SkillsBuilderModal.jsx deleted file mode 100644 index deab56c..0000000 --- a/src/skills-builder/skills-builder-modal/SkillsBuilderModal.jsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { useState, useContext } from 'react'; -import { - Button, Container, Stepper, ModalDialog, Form, Hyperlink, useMediaQuery, breakpoints, -} from '@edx/paragon'; -import { getConfig } from '@edx/frontend-platform'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { - STEP1, STEP2, -} from '../data/constants'; -import messages from './messages'; - -import { SkillsBuilderContext } from '../skills-builder-context'; -import { SkillsBuilderHeader } from '../skills-builder-header'; -import { SelectPreferences } from './select-preferences'; -import ViewResults from './view-results/ViewResults'; - -import headerImage from '../images/headerImage.png'; - -const SkillsBuilderModal = () => { - const { formatMessage } = useIntl(); - const isMedium = useMediaQuery({ maxWidth: breakpoints.medium.maxWidth }); - const { state } = useContext(SkillsBuilderContext); - const { currentGoal, currentJobTitle, careerInterests } = state; - const [currentStep, setCurrentStep] = useState(STEP1); - - const sendActionButtonEvent = (eventSuffix) => { - sendTrackEvent( - `edx.skills_builder.${eventSuffix}`, - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - current_goal: currentGoal, - current_job_title: currentJobTitle, - career_interests: careerInterests, - }, - }, - ); - }; - - const nextStepHandle = () => { - setCurrentStep(STEP2); - sendActionButtonEvent('next_step'); - }; - const exitButtonHandle = () => { - sendActionButtonEvent('exit'); - }; - const closeButtonHandle = () => { - sendActionButtonEvent('close'); - window.location.href = getConfig().MARKETING_SITE_SEARCH_URL; - }; - - return ( - - - - - { !isMedium && } - - - - - - - - - - -
- - - - - - - -
-
-
- - - - - - - - - - - - - -
-
- ); -}; - -export default SkillsBuilderModal; diff --git a/src/skills-builder/skills-builder-modal/index.js b/src/skills-builder/skills-builder-modal/index.js deleted file mode 100644 index 9bd5b7b..0000000 --- a/src/skills-builder/skills-builder-modal/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export { default as SkillsBuilderModal } from './SkillsBuilderModal'; diff --git a/src/skills-builder/skills-builder-modal/messages.js b/src/skills-builder/skills-builder-modal/messages.js deleted file mode 100644 index 66f207f..0000000 --- a/src/skills-builder/skills-builder-modal/messages.js +++ /dev/null @@ -1,32 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - /* Modal Action Row Buttons */ - goBackButton: { - id: 'go.back.button', - defaultMessage: 'Go Back', - description: 'Button that sends the user to the previous step in the skills builder.', - }, - nextStepButton: { - id: 'next.step.button', - defaultMessage: 'Next Step', - description: 'Button that sends the user to the next step in the skills builder.', - }, - exitButton: { - id: 'exit.button', - defaultMessage: 'Exit', - description: 'Button that exits the Skills Builder.', - }, - selectPreferences: { - id: 'select.preferences', - defaultMessage: 'Select preferences', - description: 'The first step of the Skills Builder for selecting a goal, a current job/occupation, and career interests', - }, - reviewResults: { - id: 'review.results', - defaultMessage: 'Review results', - description: 'The second step of the Skills Builder for rendering results from learner input', - }, -}); - -export default messages; diff --git a/src/skills-builder/skills-builder-modal/select-preferences/CareerInterestCard.jsx b/src/skills-builder/skills-builder-modal/select-preferences/CareerInterestCard.jsx deleted file mode 100644 index 1b6c33a..0000000 --- a/src/skills-builder/skills-builder-modal/select-preferences/CareerInterestCard.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; -import { - IconButton, Icon, -} from '@edx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { Close } from '@edx/paragon/icons'; -import { SkillsBuilderContext } from '../../skills-builder-context'; -import { removeCareerInterest } from '../../data/actions'; -import messages from './messages'; - -const CareerInterestCard = ({ interest }) => { - const { formatMessage } = useIntl(); - const { dispatch } = useContext(SkillsBuilderContext); - - const handleRemoveCareerInterest = () => { - dispatch(removeCareerInterest(interest)); - - sendTrackEvent( - 'edx.skills_builder.career_interest.removed', - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - career_interest: interest, - }, - }, - ); - }; - - return ( -
-

- {interest} -

- -
- ); -}; - -CareerInterestCard.propTypes = { - interest: PropTypes.string.isRequired, -}; - -export default CareerInterestCard; diff --git a/src/skills-builder/skills-builder-modal/select-preferences/CareerInterestSelect.jsx b/src/skills-builder/skills-builder-modal/select-preferences/CareerInterestSelect.jsx deleted file mode 100644 index 1fc38e7..0000000 --- a/src/skills-builder/skills-builder-modal/select-preferences/CareerInterestSelect.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useContext } from 'react'; -import { getConfig } from '@edx/frontend-platform'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { - Stack, Row, Col, Form, -} from '@edx/paragon'; -import { Configure, InstantSearch } from 'react-instantsearch-hooks-web'; -import JobTitleInstantSearch from './JobTitleInstantSearch'; -import CareerInterestCard from './CareerInterestCard'; -import { addCareerInterest } from '../../data/actions'; -import { SkillsBuilderContext } from '../../skills-builder-context'; -import messages from './messages'; - -const CareerInterestSelect = () => { - const { formatMessage } = useIntl(); - const { state, dispatch, algolia } = useContext(SkillsBuilderContext); - const { careerInterests } = state; - const { searchClient } = algolia; - - const handleCareerInterestSelect = (value) => { - if (!careerInterests.includes(value) && careerInterests.length < 3) { - dispatch(addCareerInterest(value)); - - sendTrackEvent( - 'edx.skills_builder.career_interest.added', - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - career_interest: value, - }, - }, - ); - } - }; - - return ( - - -

- {formatMessage(messages.careerInterestPrompt)} -

- - - - -
- - {careerInterests.map((interest, index) => ( - // eslint-disable-next-line react/no-array-index-key - - - - ))} - -
- ); -}; - -export default CareerInterestSelect; diff --git a/src/skills-builder/skills-builder-modal/select-preferences/GoalSelect.jsx b/src/skills-builder/skills-builder-modal/select-preferences/GoalSelect.jsx deleted file mode 100644 index 1eccfd1..0000000 --- a/src/skills-builder/skills-builder-modal/select-preferences/GoalSelect.jsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useContext } from 'react'; -import { - Form, -} from '@edx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { setGoal } from '../../data/actions'; -import { SkillsBuilderContext } from '../../skills-builder-context'; -import messages from './messages'; - -const GoalDropdown = () => { - const { formatMessage } = useIntl(); - const { state, dispatch } = useContext(SkillsBuilderContext); - const { currentGoal } = state; - - const handleGoalSelect = (e) => { - const { value } = e.target; - dispatch(setGoal(value)); - - sendTrackEvent( - 'edx.skills_builder.goal.select', - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - current_goal: value, - }, - }, - ); - }; - - return ( - - -

- {formatMessage(messages.learningGoalPrompt)} -

-
- - - - - - - - -
- ); -}; - -export default GoalDropdown; diff --git a/src/skills-builder/skills-builder-modal/select-preferences/JobTitleInstantSearch.jsx b/src/skills-builder/skills-builder-modal/select-preferences/JobTitleInstantSearch.jsx deleted file mode 100644 index 138c61e..0000000 --- a/src/skills-builder/skills-builder-modal/select-preferences/JobTitleInstantSearch.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import { - Form, -} from '@edx/paragon'; -import { useHits, useSearchBox } from 'react-instantsearch-hooks-web'; - -const JobTitleInstantSearch = (props) => { - const { refine } = useSearchBox(props); - const { hits } = useHits(props); - - const [jobInput, setJobInput] = useState(''); - - const handleAutosuggestChange = (value) => { - setJobInput(value); - }; - - useEffect(() => { - refine(jobInput); - }, [jobInput, refine]); - - return ( - - {hits.map(job => ( - - {job.name} - - ))} - - ); -}; - -JobTitleInstantSearch.propTypes = { - onSelected: PropTypes.func.isRequired, -}; - -export default JobTitleInstantSearch; diff --git a/src/skills-builder/skills-builder-modal/select-preferences/JobTitleSelect.jsx b/src/skills-builder/skills-builder-modal/select-preferences/JobTitleSelect.jsx deleted file mode 100644 index db86453..0000000 --- a/src/skills-builder/skills-builder-modal/select-preferences/JobTitleSelect.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import React, { useContext } from 'react'; -import { getConfig } from '@edx/frontend-platform'; -import { - Form, Stack, -} from '@edx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { InstantSearch } from 'react-instantsearch-hooks-web'; -import { setCurrentJobTitle } from '../../data/actions'; -import { SkillsBuilderContext } from '../../skills-builder-context'; -import JobTitleInstantSearch from './JobTitleInstantSearch'; -import messages from './messages'; - -const JobTitleSelect = () => { - const { formatMessage } = useIntl(); - const { state, dispatch, algolia } = useContext(SkillsBuilderContext); - const { searchClient } = algolia; - const { currentJobTitle } = state; - - const handleCurrentJobTitleSelect = (value) => { - dispatch(setCurrentJobTitle(value)); - sendTrackEvent( - 'edx.skills_builder.current_job.select', - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - current_job_title: value, - }, - }, - ); - }; - - const handleCheckboxChange = (e) => { - const { value } = e.target; - // only setCurrentJobTitle if the user hasn't selected a current job as we don't want to override their selection - if (!currentJobTitle) { dispatch(setCurrentJobTitle(value)); } - - sendTrackEvent( - `edx.skills_builder.current_job.${value}`, - { - app_name: 'skills_builder', - category: 'skills_builder', - }, - ); - }; - - return ( - - -

- {formatMessage(messages.jobTitlePrompt)} -

- - - -
- - - - {formatMessage(messages.studentCheckboxPrompt)} - - - {formatMessage(messages.currentlyLookingCheckboxPrompt)} - - - -
- ); -}; - -export default JobTitleSelect; diff --git a/src/skills-builder/skills-builder-modal/select-preferences/SelectPreferences.jsx b/src/skills-builder/skills-builder-modal/select-preferences/SelectPreferences.jsx deleted file mode 100644 index bf8c02c..0000000 --- a/src/skills-builder/skills-builder-modal/select-preferences/SelectPreferences.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { useContext } from 'react'; -import { - Stack, -} from '@edx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { SkillsBuilderContext } from '../../skills-builder-context'; -import GoalSelect from './GoalSelect'; -import JobTitleSelect from './JobTitleSelect'; -import CareerInterestSelect from './CareerInterestSelect'; -import messages from './messages'; - -const SelectPreferences = () => { - const { formatMessage } = useIntl(); - const { state } = useContext(SkillsBuilderContext); - const { currentGoal, currentJobTitle } = state; - - return ( - -

- {formatMessage(messages.skillsBuilderDescription)} -

- - - - - {currentGoal && ( - - )} - - {currentGoal && currentJobTitle && ( - - )} - -
- ); -}; - -export default SelectPreferences; diff --git a/src/skills-builder/skills-builder-modal/select-preferences/index.js b/src/skills-builder/skills-builder-modal/select-preferences/index.js deleted file mode 100644 index f8d553d..0000000 --- a/src/skills-builder/skills-builder-modal/select-preferences/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export { default as SelectPreferences } from './SelectPreferences'; diff --git a/src/skills-builder/skills-builder-modal/select-preferences/messages.js b/src/skills-builder/skills-builder-modal/select-preferences/messages.js deleted file mode 100644 index 8693ef0..0000000 --- a/src/skills-builder/skills-builder-modal/select-preferences/messages.js +++ /dev/null @@ -1,80 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - skillsBuilderDescription: { - id: 'skills.builder.description', - defaultMessage: 'Find the right courses and programs that help you reach your goals.', - description: 'Description of what the Skills Builder seeks to accomplish', - }, - learningGoalPrompt: { - id: 'learning.goal.prompt', - defaultMessage: 'First, tell us what you want to achieve', - description: 'Prompts the user to select their current goal.', - }, - selectLearningGoal: { - id: 'select.learning.goal', - defaultMessage: 'Select a goal', - description: 'Placeholder text for the goal selection component.', - }, - learningGoalStartCareer: { - id: 'learning.goal.start_career', - defaultMessage: 'I want to start my career', - description: 'Selected by user if their goal is to start their career.', - }, - learningGoalAdvanceCareer: { - id: 'learning.goal.advance_career', - defaultMessage: 'I want to advance my career', - description: 'Selected by user if their goal is to advance their career.', - }, - learningGoalChangeCareer: { - id: 'learning.goal.change_career', - defaultMessage: 'I want to change careers', - description: 'Selected by user if their goal is to change careers.', - }, - learningGoalSomethingNew: { - id: 'learning.goal.something.new', - defaultMessage: 'I want to learn something new', - description: 'Selected by user if their goal is to learn something new.', - }, - learningGoalSomethingElse: { - id: 'learning.goal.something.else', - defaultMessage: 'Something else', - description: 'Selected by user if their goal is not described by the other choices.', - }, - jobTitlePrompt: { - id: 'job.title.prompt', - defaultMessage: 'Next, search and select your current job title', - description: 'Prompts the user to select their current job title or occupation.', - }, - jobTitleInputPlaceholderText: { - id: 'job.title.input.placeholder.text', - defaultMessage: 'Search and select a job title', - description: 'Placeholder text for the job title input control.', - }, - studentCheckboxPrompt: { - id: 'student.checkbox.prompt', - defaultMessage: 'I\'m a student', - description: 'Label text for the corresponding checkbox', - }, - currentlyLookingCheckboxPrompt: { - id: 'currently.looking.checkbox.prompt', - defaultMessage: 'I\'m currently looking for work', - description: 'Label text for the corresponding checkbox', - }, - careerInterestPrompt: { - id: 'career.interest.prompt', - defaultMessage: 'What careers are you interested in?', - description: 'Prompts the user to select careers they are interested in pursuing.', - }, - careerInterestInputPlaceholderText: { - id: 'career.interest.input.placeholder.text', - defaultMessage: 'Select up to 3 new job titles', - description: 'Placeholder text for the career interest input control.', - }, - removeCareerInterestButtonAltText: { - id: 'career.interest.remove.button.alt.text', - defaultMessage: 'Remove career interest: ', - }, -}); - -export default messages; diff --git a/src/skills-builder/skills-builder-modal/select-preferences/test/SelectPreferences.test.jsx b/src/skills-builder/skills-builder-modal/select-preferences/test/SelectPreferences.test.jsx deleted file mode 100644 index 7f20a07..0000000 --- a/src/skills-builder/skills-builder-modal/select-preferences/test/SelectPreferences.test.jsx +++ /dev/null @@ -1,180 +0,0 @@ -import { - screen, render, cleanup, fireEvent, -} from '@testing-library/react'; -import { mergeConfig } from '@edx/frontend-platform'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { SkillsBuilderWrapperWithContext, dispatchMock, contextValue } from '../../../test/setupSkillsBuilder'; - -jest.mock('@edx/frontend-platform/analytics', () => ({ - sendTrackEvent: jest.fn(), -})); - -describe('select-preferences', () => { - beforeAll(() => { - mergeConfig({ - ALGOLIA_JOBS_INDEX_NAME: 'test-job-index-name', - }); - }); - beforeEach(() => cleanup()); - - describe('render behavior', () => { - it('should render the second prompt if a goal is selected', () => { - render( - SkillsBuilderWrapperWithContext( - { - ...contextValue, - state: { - ...contextValue.state, - currentGoal: 'I want to start my career', - }, - }, - ), - ); - const expectedGoal = { - payload: 'I want to advance my career', - type: 'SET_GOAL', - }; - const expectedStudent = { - payload: 'student', - type: 'SET_CURRENT_JOB_TITLE', - }; - - const expectedJobTitle = { - payload: 'Prospector', - type: 'SET_CURRENT_JOB_TITLE', - }; - - const goalSelect = screen.getByTestId('goal-select-dropdown'); - fireEvent.change(goalSelect, { target: { value: 'I want to advance my career' } }); - - const checkbox = screen.getByRole('checkbox', { name: 'I\'m a student' }); - fireEvent.click(checkbox); - - const jobTitleInput = screen.getByTestId('job-title-select'); - fireEvent.change(jobTitleInput, { target: { value: 'Prospector' } }); - fireEvent.click(screen.getByRole('button', { name: 'Prospector' })); - - expect(screen.getByText('Next, search and select your current job title')).toBeTruthy(); - expect(dispatchMock).toHaveBeenCalledWith(expectedGoal); - expect(dispatchMock).toHaveBeenCalledWith(expectedStudent); - expect(dispatchMock).toHaveBeenCalledWith(expectedJobTitle); - expect(sendTrackEvent).toHaveBeenCalledWith( - 'edx.skills_builder.goal.select', - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - current_goal: 'I want to advance my career', - }, - }, - ); - expect(sendTrackEvent).toHaveBeenCalledWith( - 'edx.skills_builder.current_job.student', - { - app_name: 'skills_builder', - category: 'skills_builder', - }, - ); - expect(sendTrackEvent).toHaveBeenCalledWith( - 'edx.skills_builder.current_job.select', - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - current_job_title: 'Prospector', - }, - }, - ); - }); - - it('should render the third prompt if a current job title is selected', () => { - render( - SkillsBuilderWrapperWithContext( - { - ...contextValue, - state: { - ...contextValue.state, - currentGoal: 'I want to start my career', - currentJobTitle: 'Goblin Guide', - }, - }, - ), - ); - expect(screen.getByText('What careers are you interested in?')).toBeTruthy(); - }); - - it('should render a for each career interest', () => { - render( - SkillsBuilderWrapperWithContext( - { - ...contextValue, - state: { - ...contextValue.state, - currentGoal: 'I want to start my career', - currentJobTitle: 'Goblin Lackey', - careerInterests: ['Prospector'], - }, - }, - ), - ); - expect(screen.getByText('Prospector')).toBeTruthy(); - - const careerInterestInput = screen.getByTestId('career-interest-select'); - fireEvent.change(careerInterestInput, { target: { value: 'Mirror Breaker' } }); - fireEvent.click(screen.getByRole('button', { name: 'Mirror Breaker' })); - - expect(sendTrackEvent).toHaveBeenCalledWith( - 'edx.skills_builder.career_interest.added', - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - career_interest: 'Mirror Breaker', - }, - }, - ); - expect(dispatchMock).toHaveBeenCalledWith( - { - payload: 'Mirror Breaker', - type: 'ADD_CAREER_INTEREST', - }, - ); - }); - }); - - describe('controlled behavior', () => { - it('should remove a when the corresponding close button is selected', () => { - render( - SkillsBuilderWrapperWithContext( - { - ...contextValue, - state: { - ...contextValue.state, - currentGoal: 'I want to start my career', - currentJobTitle: 'Goblin Lackey', - careerInterests: ['Prospector', 'Mirror Breaker', 'Bombardment'], - }, - }, - ), - ); - - const expected = { - payload: 'Prospector', - type: 'REMOVE_CAREER_INTEREST', - }; - - fireEvent.click(screen.getByLabelText('Remove career interest: Prospector')); - expect(dispatchMock).toHaveBeenCalledWith(expected); - expect(sendTrackEvent).toHaveBeenCalledWith( - 'edx.skills_builder.career_interest.removed', - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - career_interest: 'Prospector', - }, - }, - ); - }); - }); -}); diff --git a/src/skills-builder/skills-builder-modal/skillsBuilderModal.scss b/src/skills-builder/skills-builder-modal/skillsBuilderModal.scss deleted file mode 100644 index 10ea166..0000000 --- a/src/skills-builder/skills-builder-modal/skillsBuilderModal.scss +++ /dev/null @@ -1,22 +0,0 @@ -.skills-builder-modal { - button[aria-label="Close"][type="button"]{ - color: $white; - } -} - -$breakpoint-medium: 992px; -@media (max-width: $breakpoint-medium) { - .med-min-height { - min-height: map-get($spacers, 6); - } - .skills-builder-modal { - button[aria-label="Close"][type="button"]{ - position: relative; - top: 0.5rem; - } - } -} - -.chip-max-width { - max-width: 16rem; -} diff --git a/src/skills-builder/skills-builder-modal/view-results/CarouselStack.jsx b/src/skills-builder/skills-builder-modal/view-results/CarouselStack.jsx deleted file mode 100644 index efa8b26..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/CarouselStack.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { CardCarousel } from '@edx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import RecommendationCard from './RecommendationCard'; -import messages from './messages'; - -const CarouselStack = ({ selectedRecommendations, productTypeNames }) => { - const { formatMessage } = useIntl(); - const { id: jobId, name: jobName, recommendations } = selectedRecommendations; - const courseKeys = recommendations.course?.map(rec => ({ - title: rec.title, - courserun_key: rec.active_run_key, - })); - - const normalizeProductTypeName = (productType) => { - // If the productType is more than one word (i.e. boot_camp) - if (productType.includes('_')) { - // split to remove underscore and return an array of strings (i.e. ['boot', 'camp']) - const splitStrings = productType.split('_'); - - // map through the array and normalize each string (i.e. ['Boot', 'Camp']) - const normalizeStrings = splitStrings.map(word => word[0].toUpperCase() + word.slice(1)); - - // return the array as a string joined by white spaces (i.e. Boot Camp) - return normalizeStrings.join(' '); - } - // Otherwise, return a normalized string - const normalizeString = productType[0].toUpperCase() + productType.slice(1).toLowerCase(); - return normalizeString; - }; - - const renderCarouselTitle = (productType) => ( -

- {formatMessage(messages.productRecommendationsHeaderText, { - productType: normalizeProductTypeName(productType), - jobName, - })} -

- ); - - const handleCourseCardClick = (courseKey, productType) => { - sendTrackEvent( - 'edx.skills_builder.recommendation.click', - { - app_name: 'skills_builder', - category: 'skills_builder', - page: 'skills_builder', - courserun_key: courseKey, - product_type: productType, - selected_recommendations: { - job_id: jobId, - job_name: jobName, - courserun_keys: courseKeys, - }, - }, - ); - }; - - return ( - productTypeNames.map(productType => ( - - {recommendations[productType].map(rec => ( - - ))} - - ))); -}; - -export default CarouselStack; diff --git a/src/skills-builder/skills-builder-modal/view-results/RecommendationCard.jsx b/src/skills-builder/skills-builder-modal/view-results/RecommendationCard.jsx deleted file mode 100644 index fbd170f..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/RecommendationCard.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { - Card, Chip, Hyperlink, -} from '@edx/paragon'; -import PropTypes from 'prop-types'; -import cardImageCapFallbackSrc from '../../images/card-imagecap-fallback.png'; - -const RecommendationCard = ({ rec, productType, handleCourseCardClick }) => { - const { - card_image_url: cardImageUrl, - marketing_url: marketingUrl, - active_run_key: courseKey, - owners, - partner, - title, - } = rec; - - const { logoImageUrl } = owners[0]; - - return ( - - handleCourseCardClick(courseKey, productType)} - > - - - - {partner.map((orgName, index) => ( - // eslint-disable-next-line react/no-array-index-key - - {orgName} - - ))} - - - - ); -}; - -RecommendationCard.propTypes = { - rec: PropTypes.shape({ - title: PropTypes.string, - card_image_url: PropTypes.string, - marketing_url: PropTypes.string, - partner: PropTypes.arrayOf(PropTypes.string), - owners: PropTypes.arrayOf(PropTypes.shape({ - key: PropTypes.string, - logoImageUrl: PropTypes.string, - })), - active_run_key: PropTypes.string.isRequired, - }).isRequired, - productType: PropTypes.string.isRequired, - handleCourseCardClick: PropTypes.func.isRequired, -}; - -export default RecommendationCard; diff --git a/src/skills-builder/skills-builder-modal/view-results/RelatedSkillsSelectableBoxSet.jsx b/src/skills-builder/skills-builder-modal/view-results/RelatedSkillsSelectableBoxSet.jsx deleted file mode 100644 index 255a168..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/RelatedSkillsSelectableBoxSet.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { - SelectableBox, Chip, Stack, useMediaQuery, breakpoints, -} from '@edx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import messages from './messages'; - -const RelatedSkillsSelectableBoxSet = ({ jobSkillsList, selectedJobTitle, onChange }) => { - const { formatMessage } = useIntl(); - const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth }); - - const renderTopFiveSkills = (skills) => { - const topFiveSkills = skills.sort((a, b) => b.significance - a.significance).slice(0, 5); - return ( - topFiveSkills.map(skill => ( - - {skill.name} - - )) - ); - }; - - return ( - - {jobSkillsList.map(job => ( - -

{job.name}

- -

{formatMessage(messages.relatedSkillsHeading)}

- {renderTopFiveSkills(job.skills)} -
-
- ))} -
- ); -}; - -RelatedSkillsSelectableBoxSet.propTypes = { - jobSkillsList: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - selectedJobTitle: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, -}; - -export default RelatedSkillsSelectableBoxSet; diff --git a/src/skills-builder/skills-builder-modal/view-results/ViewResults.jsx b/src/skills-builder/skills-builder-modal/view-results/ViewResults.jsx deleted file mode 100644 index a0e4186..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/ViewResults.jsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { - useContext, useEffect, useState, -} from 'react'; -import { - Stack, Row, Alert, Spinner, -} from '@edx/paragon'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { CheckCircle, ErrorOutline } from '@edx/paragon/icons'; -import { SkillsBuilderContext } from '../../skills-builder-context'; -import RelatedSkillsSelectableBoxSet from './RelatedSkillsSelectableBoxSet'; -import messages from './messages'; -import CarouselStack from './CarouselStack'; - -import { getRecommendations } from './data/service'; -import { useProductTypes } from './data/hooks'; - -const ViewResults = () => { - const { formatMessage } = useIntl(); - const { algolia, state } = useContext(SkillsBuilderContext); - const { jobSearchIndex, productSearchIndex } = algolia; - const { careerInterests } = state; - - const [selectedJobTitle, setSelectedJobTitle] = useState(''); - const [jobSkillsList, setJobSkillsList] = useState([]); - const [productRecommendations, setProductRecommendations] = useState([]); - const [selectedRecommendations, setSelectedRecommendations] = useState({}); - const [isLoading, setIsLoading] = useState(true); - const [fetchError, setFetchError] = useState(false); - - const productTypes = useProductTypes(); - - useEffect(() => { - const getAllRecommendations = async () => { - // eslint-disable-next-line max-len - const { jobInfo, results } = await getRecommendations(jobSearchIndex, productSearchIndex, careerInterests, productTypes); - - setJobSkillsList(jobInfo); - setSelectedJobTitle(results[0].name); - setProductRecommendations(results); - setIsLoading(false); - sendTrackEvent('edx.skills_builder.recommendation.shown', { - app_name: 'skills_builder', - category: 'skills_builder', - page: 'skills_builder', - selected_recommendations: { - job_id: results[0].id, - job_name: results[0].name, - /* We extract the title and course key into an array of objects */ - courserun_keys: results[0].recommendations.course?.map(rec => ({ - title: rec.title, - courserun_key: rec.active_run_key, - })), - }, - is_default: true, - }); - }; - - getAllRecommendations() - .catch(() => { - setFetchError(true); - setIsLoading(false); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [careerInterests, jobSearchIndex, productSearchIndex]); - - useEffect(() => { - setSelectedRecommendations(productRecommendations.find(rec => rec.name === selectedJobTitle)); - }, [productRecommendations, selectedJobTitle]); - - const handleJobTitleChange = (e) => { - const { value } = e.target; - setSelectedJobTitle(value); - const currentSelection = productRecommendations.find(rec => rec.name === value); - const { id: jobId, name: jobName, recommendations } = currentSelection; - const courseKeys = recommendations.course?.map(rec => ({ - title: rec.title, - courserun_key: rec.active_run_key, - })); - /* - The is_default value will be set to false for any selections made by the user. - This code is intentionally duplicated from the event that fires in the useEffect for fetching recommendations. - This proved less clunky than refactoring to make things DRY as we have to ensure the first call fires only once. - The previous implementation wrapped the event in an additional useEffect that was looping unnecessarily. - We have plans to refactor all of the event code as part of APER-2392, where we will revisit this approach. - */ - sendTrackEvent('edx.skills_builder.recommendation.shown', { - app_name: 'skills_builder', - category: 'skills_builder', - page: 'skills_builder', - selected_recommendations: { - job_id: jobId, - job_name: jobName, - courserun_keys: courseKeys, - }, - is_default: false, - }); - }; - - if (fetchError) { - return ( - - - {formatMessage(messages.matchesNotFoundDangerAlert)} - - - ); - } - - return ( - isLoading ? ( - - - - ) : ( - - - - {formatMessage(messages.matchesFoundSuccessAlert)} - - - - - - - - ) - ); -}; - -export default ViewResults; diff --git a/src/skills-builder/skills-builder-modal/view-results/data/constants.js b/src/skills-builder/skills-builder-modal/view-results/data/constants.js deleted file mode 100644 index 182f783..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/data/constants.js +++ /dev/null @@ -1,14 +0,0 @@ -export const COURSE = 'course'; -const BOOT_CAMP = 'boot_camp'; -const EXECUTIVE_EDUCATION = 'executive_education'; -const DEGREE = '2U_degree'; -const PROGRAM = 'program'; - -// This array is used to determine the validity of product types as they are passed through the query string -export const productTypes = [ - DEGREE, - BOOT_CAMP, - EXECUTIVE_EDUCATION, - PROGRAM, - COURSE, -]; diff --git a/src/skills-builder/skills-builder-modal/view-results/data/hooks.js b/src/skills-builder/skills-builder-modal/view-results/data/hooks.js deleted file mode 100644 index adba093..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/data/hooks.js +++ /dev/null @@ -1,39 +0,0 @@ -import { useLocation } from 'react-router-dom'; -import { productTypes as acceptedProductTypes, COURSE } from './constants'; - -const defaultSetting = [COURSE]; - -/* - * Hook that calls the useLocation() hook from react-router-dom to have a reference to the query string in the URL. - * The returned array determines the order in which the recommendations will appear to the user. - * - * @return {Array[String]} productTypes - An array of strings that represent each line of business - */ -// eslint-disable-next-line import/prefer-default-export -export const useProductTypes = () => { - const { search } = useLocation(); - const checkedTypes = []; - - if (search) { - // remove the "?" and split the query string at "=" - const splitString = search.slice(1).split('='); - - // if the key is not "product_types", use a default setting - if (splitString[0] !== 'product_types') { - return defaultSetting; - } - - // split productTypes string into an array at "," - const queryProductTypes = splitString[1]?.split(','); - - // compare each product type from the query string with a list of accepted product types - queryProductTypes.forEach(productType => { - if (acceptedProductTypes.includes(productType)) { - checkedTypes.push(productType); - } - }); - } - - // if no types were set, use default setting - return checkedTypes.length > 0 ? checkedTypes : defaultSetting; -}; diff --git a/src/skills-builder/skills-builder-modal/view-results/data/service.js b/src/skills-builder/skills-builder-modal/view-results/data/service.js deleted file mode 100644 index 369edbb..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/data/service.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import { searchJobs, getProductRecommendations } from '../../../utils/search'; - -export async function getRecommendations(jobSearchIndex, productSearchIndex, careerInterests, productTypes) { - const jobInfo = await searchJobs(jobSearchIndex, careerInterests); - - const results = await Promise.all(jobInfo.map(async (job) => { - const formattedSkills = job.skills.map(skill => skill.name); - - // create a data object for each job - const data = { - id: job.id, - name: job.name, - recommendations: {}, - }; - - // get recommendations for each product type based on the skills for the current job - - await Promise.all(productTypes.map(async (productType) => { - const formattedProductType = productType.replace('_', ' '); - const response = await getProductRecommendations(productSearchIndex, formattedProductType, formattedSkills); - - // add a new key to the recommendations object and set the value to the response - data.recommendations[productType] = response; - })); - - return data; - })); - - return { - jobInfo, - results, - }; -} diff --git a/src/skills-builder/skills-builder-modal/view-results/data/test/hooks.test.jsx b/src/skills-builder/skills-builder-modal/view-results/data/test/hooks.test.jsx deleted file mode 100644 index e9ee5b0..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/data/test/hooks.test.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; -import { useProductTypes } from '../hooks'; - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: jest.fn(() => ({ search: global.query_string || '' })), -})); - -describe('useProductTypes', () => { - test('returns default setting if no query string is provided', () => { - const { result } = renderHook(() => useProductTypes()); - - const productTypes = result.current; - - expect(productTypes).toEqual(['course']); - }); - - test('returns a list of settings when serialized correctly', () => { - global.query_string = '?product_types=boot_camp,course'; - - const { result } = renderHook(() => useProductTypes()); - - const productTypes = result.current; - - expect(productTypes).toEqual(['boot_camp', 'course']); - }); - - test('returns the default setting if query string is not serialized correctly', () => { - global.query_string = '?legend_of_zelda=boot_camp,course'; - const { result } = renderHook(() => useProductTypes()); - - const productTypes = result.current; - - expect(productTypes).toEqual(['course']); - }); - - test('returns a filtered list if unrecognized values are provided', () => { - global.query_string = '?product_types=boot_camp,course,hack_the_mainframe'; - - const { result } = renderHook(() => useProductTypes()); - - const productTypes = result.current; - - expect(productTypes).toEqual(['boot_camp', 'course']); - }); -}); diff --git a/src/skills-builder/skills-builder-modal/view-results/index.js b/src/skills-builder/skills-builder-modal/view-results/index.js deleted file mode 100644 index b0af023..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/index.js +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export { default as ViewResults } from './ViewResults'; diff --git a/src/skills-builder/skills-builder-modal/view-results/messages.js b/src/skills-builder/skills-builder-modal/view-results/messages.js deleted file mode 100644 index 91660d6..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/messages.js +++ /dev/null @@ -1,31 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - matchesFoundSuccessAlert: { - id: 'matches.found.success.alert', - defaultMessage: 'We found skills and courses that match your preferences!', - description: 'Success alert message to display when recommendations are presented to the learner.', - }, - matchesNotFoundDangerAlert: { - id: 'matches.not.found.danger.alert', - defaultMessage: 'We were not able to retrieve recommendations at this time. Please try again later.', - description: 'Danger alert message to display when the component fails to get recommendations.', - }, - relatedSkillsHeading: { - id: 'related.skills.heading', - defaultMessage: 'Related Skills', - description: 'Heading text for a selectable box that displays related skills for a corresponding selected job title.', - }, - relatedSkillsSelectableBoxLabelText: { - id: 'related.skills.selectable.box.label.text', - defaultMessage: 'Related skills:', - description: 'Label text for a selectable box that displays related skills for a corresponding selected job title.', - }, - productRecommendationsHeaderText: { - id: 'product.recommendations.header.text', - defaultMessage: '{productType} recommendations for {jobName}', - description: 'Header text for a carousel of product recommendations.', - }, -}); - -export default messages; diff --git a/src/skills-builder/skills-builder-modal/view-results/test/ViewResults.test.jsx b/src/skills-builder/skills-builder-modal/view-results/test/ViewResults.test.jsx deleted file mode 100644 index d4df5fa..0000000 --- a/src/skills-builder/skills-builder-modal/view-results/test/ViewResults.test.jsx +++ /dev/null @@ -1,179 +0,0 @@ -import { - screen, render, cleanup, fireEvent, act, -} from '@testing-library/react'; -import { mergeConfig } from '@edx/frontend-platform'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { SkillsBuilderWrapperWithContext, contextValue } from '../../../test/setupSkillsBuilder'; -import { getProductRecommendations } from '../../../utils/search'; - -jest.mock('@edx/frontend-platform/analytics', () => ({ - sendTrackEvent: jest.fn(), -})); - -const renderSkillsBuilderWrapper = ( - value = { - ...contextValue, - state: { - ...contextValue.state, - currentGoal: 'I want to start my career', - currentJobTitle: 'Goblin Lackey', - careerInterests: ['Prospector', 'Mirror Breaker', 'Bombardment'], - }, - }, -) => { - render(SkillsBuilderWrapperWithContext(value)); -}; - -describe('view-results', () => { - beforeAll(() => { - mergeConfig({ - ALGOLIA_JOBS_INDEX_NAME: 'test-job-index-name', - }); - }); - - describe('user interface', () => { - beforeEach(async () => { - cleanup(); - // Render the form filled out - renderSkillsBuilderWrapper(); - // Click the next button to trigger "fetching" the data - await act(async () => { - fireEvent.click(screen.getByRole('button', { name: 'Next Step' })); - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should render a for each career interest the learner has submitted', () => { - expect(screen.getByText('Prospector')).toBeTruthy(); - expect(screen.getByText('Mirror Breaker')).toBeTruthy(); - - const chipComponents = document.querySelectorAll('.pgn__chip'); - expect(chipComponents[0].textContent).toEqual('finding shiny things'); - expect(chipComponents[1].textContent).toEqual('mining'); - expect(sendTrackEvent).toHaveBeenCalledWith( - 'edx.skills_builder.recommendation.shown', - { - app_name: 'skills_builder', - category: 'skills_builder', - page: 'skills_builder', - selected_recommendations: { - job_id: 0, - job_name: 'Prospector', - courserun_keys: [ - { - title: 'Mining with the Mons', - courserun_key: 'MONS101', - }, - { - title: 'The Art of Warren Upkeep', - courserun_key: 'WAR101', - }, - ], - }, - is_default: true, - }, - ); - // called once when "Next Step" button is clicked and then again for above event - expect(sendTrackEvent).toHaveBeenCalledTimes(2); - }); - - it('renders a carousel of components', () => { - expect(screen.getByText('Course recommendations for Prospector')).toBeTruthy(); - }); - - it('changes the recommendations based on the selected job title', () => { - fireEvent.click(screen.getByRole('radio', { name: 'Mirror Breaker' })); - expect(screen.getByText('Course recommendations for Mirror Breaker')).toBeTruthy(); - expect(sendTrackEvent).toHaveBeenCalledWith( - 'edx.skills_builder.recommendation.shown', - { - app_name: 'skills_builder', - category: 'skills_builder', - page: 'skills_builder', - selected_recommendations: { - job_id: 1, - job_name: 'Mirror Breaker', - courserun_keys: [ - { - title: 'Mining with the Mons', - courserun_key: 'MONS101', - }, - { - title: 'The Art of Warren Upkeep', - courserun_key: 'WAR101', - }, - ], - }, - is_default: false, - }, - ); - }); - - it('sends an event when the "Next Step" button is clicked', () => { - expect(sendTrackEvent).toHaveBeenCalledWith( - 'edx.skills_builder.next_step', - { - app_name: 'skills_builder', - category: 'skills_builder', - learner_data: { - current_goal: 'I want to start my career', - current_job_title: 'Goblin Lackey', - career_interests: ['Prospector', 'Mirror Breaker', 'Bombardment'], - }, - }, - ); - }); - - it('fires an event when a product recommendation is clicked', () => { - fireEvent.click(screen.getByText('Mining with the Mons')); - expect(sendTrackEvent).toHaveBeenCalledWith( - 'edx.skills_builder.recommendation.click', - { - app_name: 'skills_builder', - category: 'skills_builder', - page: 'skills_builder', - courserun_key: 'MONS101', - product_type: 'course', - selected_recommendations: { - job_id: 0, - job_name: 'Prospector', - courserun_keys: [ - { - title: 'Mining with the Mons', - courserun_key: 'MONS101', - }, - { - title: 'The Art of Warren Upkeep', - courserun_key: 'WAR101', - }, - ], - }, - }, - ); - }); - }); - - describe('fetch recommendations', () => { - beforeEach(() => { - cleanup(); - // Render the form filled out - renderSkillsBuilderWrapper(); - }); - - it('renders an alert if an error is thrown while fetching', async () => { - getProductRecommendations.mockImplementationOnce(() => { - throw new Error(); - }); - - // Click the next button to trigger "fetching" the data - await act(async () => { - fireEvent.click(screen.getByRole('button', { name: 'Next Step' })); - }); - - expect(screen.getByText('We were not able to retrieve recommendations at this time. Please try again later.')).toBeTruthy(); - }); - }); -}); diff --git a/src/skills-builder/test/SkillsBuilder.test.jsx b/src/skills-builder/test/SkillsBuilder.test.jsx deleted file mode 100644 index 6803376..0000000 --- a/src/skills-builder/test/SkillsBuilder.test.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import React from 'react'; -import { - screen, render, act, -} from '@testing-library/react'; -import { SkillsBuilder } from '..'; -import { SkillsBuilderProvider } from '../skills-builder-context'; - -describe('skills-builder', () => { - it('should render a Skills Builder modal with a prompt for the user', () => { - act(() => { - render( - - - - - , - ); - }); - expect(screen.getByText('Skills Builder')).toBeTruthy(); - expect(screen.getByText('First, tell us what you want to achieve')).toBeTruthy(); - }); -}); diff --git a/src/skills-builder/test/__mocks__/jobSkills.mockData.js b/src/skills-builder/test/__mocks__/jobSkills.mockData.js deleted file mode 100644 index 6d43f31..0000000 --- a/src/skills-builder/test/__mocks__/jobSkills.mockData.js +++ /dev/null @@ -1,69 +0,0 @@ -export const mockData = { - hits: [ - { - id: 0, - name: 'Prospector' - }, - { - id: 1, - name: 'Mirror Breaker' - }, - ], - searchJobs: [ - { - id: 0, - name: 'Prospector', - skills: [ - { external_id: 0, - name: 'mining', - significance: 50, - }, - { external_id: 1, - name: 'finding shiny things', - significance: 100, - }], - }, - { - id: 1, - name: 'Mirror Breaker', - skills: [ - { external_id: 0, - name: 'mining', - significance: 50, - }, - { external_id: 1, - name: 'finding shiny things', - significance: 100, - }], - }, - ], - productRecommendations: [ - { - title: 'Mining with the Mons', - uuid: 'thisIsARandomString01', - partner: ['edx'], - card_image_url: 'https://thisIsAUrl.ForAnImage.01.jpeg', - marketing_url: 'https://thisIsAUrl.ForTheRecommendedContent.01.com', - active_run_key: 'MONS101', - owners: [ - { - logoImageUrl: 'https://thisIsAUrl.ForALogoImage.01.jpeg', - } - ] - }, - { - title: 'The Art of Warren Upkeep', - uuid: 'thisIsARandomString02', - partner: ['edx'], - card_image_url: 'https://thisIsAUrl.ForAnImage.02.jpeg', - marketing_url: 'https://thisIsAUrl.ForTheRecommendedContent.02.com', - active_run_key: 'WAR101', - owners: [ - { - logoImageUrl: 'https://thisIsAUrl.ForALogoImage.02.jpeg', - } - ] - }, - ], - useAlgoliaSearch: [{}, {}, {}], -}; diff --git a/src/skills-builder/test/setupSkillsBuilder.jsx b/src/skills-builder/test/setupSkillsBuilder.jsx deleted file mode 100644 index 2e3b6c7..0000000 --- a/src/skills-builder/test/setupSkillsBuilder.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import React from 'react'; -import { SkillsBuilderModal } from '../skills-builder-modal'; -import { SkillsBuilderContext } from '../skills-builder-context'; -import { skillsInitialState } from '../data/reducer'; -import { mockData } from './__mocks__/jobSkills.mockData'; -import { getProductRecommendations, searchJobs, useAlgoliaSearch } from '../utils/search'; - -jest.mock('@edx/frontend-platform/logging'); - -jest.mock('react-instantsearch-hooks-web', () => ({ - // eslint-disable-next-line react/prop-types - InstantSearch: ({ children }) => (
{children}
), - Configure: jest.fn(() => (null)), - useSearchBox: jest.fn(() => ({ refine: jest.fn() })), - useHits: jest.fn(() => ({ hits: mockData.hits })), -})); - -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: jest.fn(() => ({ search: '?query_string=values' })), -})); - -jest.mock('../utils/search', () => ({ - searchJobs: jest.fn(), - getProductRecommendations: jest.fn(), - useAlgoliaSearch: jest.fn(), -})); - -searchJobs.mockReturnValue(mockData.searchJobs); -getProductRecommendations.mockReturnValue(mockData.productRecommendations); -useAlgoliaSearch.mockReturnValue(mockData.useAlgoliaSearch); - -export const dispatchMock = jest.fn(); - -export const contextValue = { - state: { - ...skillsInitialState, - }, - dispatch: dispatchMock, - algolia: { - // Without this, tests would fail to destructure `searchClient` in the component - searchClient: {}, - productSearchIndex: {}, - jobSearchIndex: {}, - }, -}; - -export const SkillsBuilderWrapperWithContext = (value = contextValue) => ( - - - - - -); diff --git a/src/skills-builder/utils/search.js b/src/skills-builder/utils/search.js deleted file mode 100644 index 06a555a..0000000 --- a/src/skills-builder/utils/search.js +++ /dev/null @@ -1,110 +0,0 @@ -/* -Algolia utility functions used by the Skills Builder feature. -*/ -import { useMemo } from 'react'; -import { getConfig } from '@edx/frontend-platform'; -import { logError } from '@edx/frontend-platform/logging'; - -import algoliasearch from 'algoliasearch'; - -/* - * Utility function to create and return an Algolia client, as well as Index objects for our product and job data. - * - * @return {SearchClient} searchClient - An instantiated Algolia client - * @return {SearchIndex} productSearchIndex - An Algolia index of product data. Used to retrieve product - * recommendations for learners - * @return {SearchIndex} jobSearchIndex - An Algolia index of job taxonomy data. Used to retrieve job metadata that a - * learner is interested in. - */ -// eslint-disable-next-line import/prefer-default-export -export const useAlgoliaSearch = () => { - const config = getConfig(); - - const [searchClient, productSearchIndex, jobSearchIndex] = useMemo( - () => { - const client = algoliasearch( - config.ALGOLIA_APP_ID, - config.ALGOLIA_SEARCH_API_KEY, - ); - const productIndex = client.initIndex(config.ALGOLIA_PRODUCT_INDEX_NAME); - const jobIndex = client.initIndex(config.ALGOLIA_JOBS_INDEX_NAME); - return [client, productIndex, jobIndex]; - }, - [ - config.ALGOLIA_APP_ID, - config.ALGOLIA_PRODUCT_INDEX_NAME, - config.ALGOLIA_JOBS_INDEX_NAME, - config.ALGOLIA_SEARCH_API_KEY, - ], - ); - return [searchClient, productSearchIndex, jobSearchIndex]; -}; - -/* - * Utility function used to format a list of data so it matches syntax Algolia expects. - * - * @param {String} facetFilterType - A string declaring the facet filter type to prepend each search item (e.g. `name`) - * @param {Array[String]} data - An array of job or skills used to query data in Algolia. - * - * @return {Array[String]} formattedData - The transformed array of data to search prepended with the facet filter type - */ -export function formatFacetFilterData(facetFilterType, data) { - const formattedData = []; - if (data) { - data.forEach(item => formattedData.push(`${facetFilterType}:${item}`)); - } - - return formattedData; -} - -/* - * Utility function responsible for querying and returning job information based on input received from a learner. - * - * @param {SearchIndex} jobIndex - An Algolia index of taxonomy connector data used to retrieve job information a - * learner is interested in - * @param {Array[String]} jobNames - A list of job names a learner is interested in - * - * @return {Array[Object]} results - Job information retrieved from Algolia - */ -export const searchJobs = async (jobIndex, jobNames) => { - const formattedJobNames = formatFacetFilterData('name', jobNames); - try { - const { hits } = await jobIndex.search('', { - facetFilters: [ - formattedJobNames, - ], - }); - return hits; - } catch (error) { - logError(error); - } - - return []; -}; - -/* - * Utility function responsible for returning recommendations on products based on the skills of a job a learner is - * interested in. - * - * @param {SearchIndex} productIndex - An Algolia index of product data used to retrieve recommendations for learners. - * @param {String} productType - The type of product information you are trying to retrieve (e.g. `course` or `program`) - * @param {Array[String]} skills - An array of skill names related to a job/career a learner expressed interest in - * - * @return {Array[Object]} results - Product information retrieved from Algolia - */ -export const getProductRecommendations = async (productIndex, productType, skills) => { - const formattedSkillNames = formatFacetFilterData('skills.skill', skills); - try { - const { hits } = await productIndex.search('', { - filters: `product: "${productType}" AND language: "English"`, - facetFilters: [ - formattedSkillNames, - ], - }); - return hits; - } catch (error) { - logError(error); - } - - return []; -}; diff --git a/src/skills-builder/utils/tests/search.test.js b/src/skills-builder/utils/tests/search.test.js deleted file mode 100644 index 1e3edbd..0000000 --- a/src/skills-builder/utils/tests/search.test.js +++ /dev/null @@ -1,74 +0,0 @@ -import { - formatFacetFilterData, - getProductRecommendations, - searchJobs, -} from '../search'; - -jest.mock('@edx/frontend-platform/logging'); - -const mockAlgoliaResult = { - hits: [ - { - key: 'test-course-key', - title: 'Test Title', - skill_names: [ - { - id: 1, - name: 'Skill Name', - }, - ], - }, - ], -}; - -const mockIndex = { - search: jest.fn().mockImplementation(() => mockAlgoliaResult), -}; - -describe('Algolias utility function', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('formatFacetFilterData() should return a new array with data formatted as expected', () => { - const result = formatFacetFilterData('name', ['Organic Farmer']); - expect(result).toEqual(['name:Organic Farmer']); - }); - - it('searchJobs() queries Algolia with the expected search parameters', async () => { - const expectedSearchParameters = { - facetFilters: [ - ['name:Enchanter'], - ], - }; - - const results = await searchJobs(mockIndex, ['Enchanter']); - expect(mockIndex.search).toHaveBeenCalledTimes(1); - expect(mockIndex.search).toHaveBeenCalledWith('', expectedSearchParameters); - expect(results).toEqual(mockAlgoliaResult.hits); - }); - - it('searchJobs() returns an empty array when an exception occurs querying Algolia', async () => { - const results = await searchJobs(null, ['Organic Farmer']); - expect(results).toEqual([]); - }); - - it('getProductRecommendations() queries Algolia with the expected search parameters', async () => { - const expectedSearchParameters = { - filters: 'product: "Course" AND language: "English"', - facetFilters: [ - ['skills.skill:Sword Lobbing'], - ], - }; - - const results = await getProductRecommendations(mockIndex, 'Course', ['Sword Lobbing']); - expect(mockIndex.search).toHaveBeenCalledTimes(1); - expect(mockIndex.search).toHaveBeenCalledWith('', expectedSearchParameters); - expect(results).toEqual(mockAlgoliaResult.hits); - }); - - it('getProductRecommendations() returns an empty array when an exception occurs querying Algolia', async () => { - const results = await getProductRecommendations(null, 'Course', ['Management']); - expect(results).toEqual([]); - }); -});