Compare commits

...

41 Commits

Author SHA1 Message Date
David Joy
e91cdacfc9 feat: adding pact testing for APIs
This is WIP and shouldn’t make it to master.
2023-06-12 17:09:07 -04:00
renovate[bot]
6740ad3672 fix(deps): update dependency algoliasearch to v4.17.2 2023-06-12 16:25:54 +00:00
renovate[bot]
ca14d3d279 chore(deps): update dependency @edx/frontend-build to v12.8.51 2023-06-12 12:00:55 +00:00
Jenkins
9dbe58b10f chore(i18n): update translations 2023-06-11 16:40:53 -04:00
Jason Wesson
50d80ef614 fix: adjust header height and content when window is medium in width or less (#756)
* fix: adjust header height and content when window is medium in width or less

* feat: convert stepper header's contents to compact view for medium windows (or less)

---------

Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-06-06 11:10:00 -07:00
renovate[bot]
fe6b76da7e fix(deps): update dependency @edx/frontend-platform to v4.5.1 2023-06-05 12:46:05 +00:00
renovate[bot]
7fff13137d chore(deps): update dependency @edx/frontend-build to v12.8.40 2023-06-05 10:26:23 +00:00
renovate[bot]
91282cff74 chore(deps): update commitlint monorepo to v17.6.5 2023-06-05 07:50:24 +00:00
renovate[bot]
45be830f18 fix(deps): update dependency core-js to v3.30.2 2023-05-29 12:15:11 +00:00
renovate[bot]
122affbb6d fix(deps): update dependency @edx/frontend-platform to v4.5.0 2023-05-29 11:07:30 +00:00
renovate[bot]
48a97b769f fix(deps): update dependency algoliasearch to v4.17.1 2023-05-29 08:24:04 +00:00
renovate[bot]
bdcc09f6ba chore(deps): update dependency @edx/frontend-build to v12.8.38 2023-05-29 06:05:47 +00:00
Maxwell Frank
ac4fb6a340 Merge pull request #757 from openedx/mfrank/fallback-img-typo
fix: fallback src typo for course cards
2023-05-25 13:01:59 -04:00
Maxwell Frank
409d365125 fix: fallback src typo for course cards 2023-05-25 16:15:20 +00:00
Jason Wesson
53985e94d8 fix: disable 'select a goal' from goal dropdown when a goal has been selected (#755)
* hide the second and third portion of skills form if currentGoal is false
2023-05-23 07:57:25 -07:00
renovate[bot]
0d9a39afd7 fix(deps): update dependency @edx/frontend-platform to v4.4.0 2023-05-23 04:18:26 +00:00
renovate[bot]
cbb860bb16 chore(deps): update dependency @edx/reactifex to v2.2.0 2023-05-23 01:34:22 +00:00
Justin Hynes
695df9aa0b fix: filter out non-English courses from recommendations (#752)
[APER-2444]

This PR updates the `getProductRecommendations` utiltiy function, adding a filter to only include English courses in our recommendations.
2023-05-22 13:13:43 -04:00
Maxwell Frank
603304b799 Merge pull request #751 from openedx/mfrank/fix-close-exit-buttons
fix: close and exit buttons
2023-05-18 13:58:42 -04:00
Maxwell Frank
d3e5931d05 fix: close and exit buttons 2023-05-18 17:52:40 +00:00
renovate[bot]
6804f7e127 fix(deps): update dependency algoliasearch to v4.17.0 2023-05-17 22:25:05 +00:00
Maxwell Frank
4b16673780 Merge pull request #750 from openedx/mfrank/reduce-event-payload
fix: reduce event payload
2023-05-17 11:12:10 -04:00
Maxwell Frank
6674025bd4 fix: reduce event payload 2023-05-17 14:38:15 +00:00
renovate[bot]
0dab2d03eb chore(deps): update commitlint monorepo to v17.6.3 2023-05-15 09:41:55 +00:00
Jenkins
df1a84feb7 chore(i18n): update translations 2023-05-14 16:40:48 -04:00
Bilal Qamar
334a9b090e feat: upgraded to node v18, added .nvmrc and updated workflows (#712)
* feat: upgraded to node v18, added .nvmrc and updated workflows

* build: updated frontend-build, frontend-platform, component-footer & component-header packages

* refactor: updated snapshots

* refactor: updated snapshots
2023-05-12 14:09:48 -04:00
Maxwell Frank
5d06276838 Merge pull request #745 from openedx/mfrank/adding-configure-component
fix: remove filtering from current job select
2023-05-11 09:20:43 -04:00
Maxwell Frank
e391e427f1 fix: remove filtering from current job select 2023-05-11 13:05:04 +00:00
Maxwell Frank
b71328fd3f Merge pull request #744 from openedx/mfrank/adding-configure-component
fix: adding configure component to filter jobs
2023-05-10 16:40:11 -04:00
Maxwell Frank
3b9b3f8840 fix: adding configure component to filter jobs 2023-05-10 20:34:29 +00:00
Maxwell Frank
30e837306f Merge pull request #739 from openedx/mfrank/segement-events
APER-2333: Dispatch segment events from JS components
2023-05-10 15:45:35 -04:00
Maxwell Frank
c7a0c1d799 feat: adding analytics 2023-05-09 13:29:38 +00:00
renovate[bot]
337c97e3a0 chore(deps): update dependency @edx/frontend-build to v12.8.27 2023-05-08 09:32:26 +00:00
renovate[bot]
0de4496953 fix(deps): update dependency @edx/paragon to v20.32.3 2023-05-01 10:06:44 +00:00
renovate[bot]
359ae7f1fb chore(deps): update dependency @edx/frontend-build to v12.8.16 2023-05-01 07:10:23 +00:00
Omar Al-Ithawi
8d467f01dc feat: use atlas in make pull_translations (#732)
- Bump frontend-platform to bring intl-imports.js script
 - Move all i18n imports into `src/i18n/index.js` so intl-imports.js can override it with latest translations
 - Add `atlas` into `make pull_translations` when `OPENEDX_ATLAS_PULL` environment variable is set.

This pull request is part of the [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) which is sparked by the [Translation Infrastructure update OEP-58](https://open-edx-proposals.readthedocs.io/en/latest/architectural-decisions/oep-0058-arch-translations-management.html#specification).
2023-04-25 09:33:22 -04:00
renovate[bot]
20debcd79e fix(deps): update dependency reselect to v4.1.8 2023-04-24 13:13:42 +00:00
renovate[bot]
6a7cbf88df fix(deps): update dependency @edx/frontend-component-footer to v11.7.4 2023-04-24 09:34:43 +00:00
Jenkins
1b3880ee1b chore(i18n): update translations 2023-04-23 16:40:44 -04:00
renovate[bot]
79cebaf6df fix(deps): update dependency @edx/frontend-component-footer to v11.7.2 2023-04-17 15:00:56 +00:00
renovate[bot]
8686af563e chore(deps): update dependency @edx/frontend-build to v12.8.10 2023-04-17 10:24:54 +00:00
45 changed files with 5039 additions and 19309 deletions

1
.env
View File

@@ -25,6 +25,7 @@ FAVICON_URL=''
COLLECT_YEAR_OF_BIRTH=true COLLECT_YEAR_OF_BIRTH=true
APP_ID='' APP_ID=''
MFE_CONFIG_API_URL='' MFE_CONFIG_API_URL=''
SEARCH_CATALOG_URL=''
ENABLE_SKILLS_BUILDER='' ENABLE_SKILLS_BUILDER=''
ENABLE_SKILLS_BUILDER_PROFILE='' ENABLE_SKILLS_BUILDER_PROFILE=''
ALGOLIA_APP_ID='' ALGOLIA_APP_ID=''

View File

@@ -26,6 +26,7 @@ FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
COLLECT_YEAR_OF_BIRTH=true COLLECT_YEAR_OF_BIRTH=true
APP_ID='' APP_ID=''
MFE_CONFIG_API_URL='' MFE_CONFIG_API_URL=''
SEARCH_CATALOG_URL='http://localhost:18000/courses'
ENABLE_SKILLS_BUILDER='true' ENABLE_SKILLS_BUILDER='true'
ENABLE_SKILLS_BUILDER_PROFILE='' ENABLE_SKILLS_BUILDER_PROFILE=''
ALGOLIA_APP_ID='' ALGOLIA_APP_ID=''

View File

@@ -13,13 +13,13 @@ jobs:
- i18n_extract - i18n_extract
- lint - lint
- test - test
node: [16]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node }} node-version: ${{ env.NODE_VER }}
- run: npm install -g npm@8.x.x
- run: make requirements - run: make requirements
- run: make test NPM_TESTS=build - run: make test NPM_TESTS=build
- run: make test NPM_TESTS=${{ matrix.npm-test }} - run: make test NPM_TESTS=${{ matrix.npm-test }}

View File

@@ -10,4 +10,4 @@ on:
jobs: jobs:
version-check: version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master

2
.nvmrc
View File

@@ -1 +1 @@
v16 18

View File

@@ -2,6 +2,7 @@ export TRANSIFEX_RESOURCE = frontend-app-profile
transifex_resource = frontend-app-profile transifex_resource = frontend-app-profile
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA" transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA"
intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json transifex_input = $(i18n)/transifex_input.json
@@ -49,9 +50,23 @@ push_translations:
# Pushing comments to Transifex... # Pushing comments to Transifex...
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh ./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
ifeq ($(OPENEDX_ATLAS_PULL),)
# Pulls translations from Transifex. # Pulls translations from Transifex.
pull_translations: pull_translations:
tx pull -t -f --mode reviewed --languages=$(transifex_langs) tx pull -t -f --mode reviewed --languages=$(transifex_langs)
else
# Experimental: OEP-58 Pulls translations using atlas
pull_translations:
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull --filter=$(transifex_langs) \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-app-profile/src/i18n/messages:frontend-app-profile
$(intl_imports) frontend-component-header frontend-component-footer frontend-app-profile
endif
# This target is used by Travis. # This target is used by Travis.
validate-no-uncommitted-package-lock-changes: validate-no-uncommitted-package-lock-changes:

23439
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,18 +28,19 @@
], ],
"dependencies": { "dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.2.0", "@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/frontend-component-footer": "11.7.1", "@edx/frontend-component-footer": "12.0.0",
"@edx/frontend-component-header": "3.6.5", "@edx/frontend-component-header": "4.0.0",
"@edx/frontend-platform": "3.3.1", "@edx/frontend-platform": "4.5.1",
"@edx/paragon": "^20.20.0", "@edx/paragon": "^20.43.0",
"@fortawesome/fontawesome-svg-core": "1.2.36", "@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4", "@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.2.0", "@fortawesome/react-fontawesome": "0.2.0",
"algoliasearch": "4.6.0", "@pact-foundation/pact": "^11.0.2",
"algoliasearch": "4.17.2",
"classnames": "2.3.2", "classnames": "2.3.2",
"core-js": "3.27.2", "core-js": "3.30.2",
"history": "4.10.1", "history": "4.10.1",
"lodash.camelcase": "4.3.0", "lodash.camelcase": "4.3.0",
"lodash.get": "4.4.2", "lodash.get": "4.4.2",
@@ -59,15 +60,15 @@
"redux-saga": "1.2.3", "redux-saga": "1.2.3",
"redux-thunk": "2.4.2", "redux-thunk": "2.4.2",
"regenerator-runtime": "0.13.11", "regenerator-runtime": "0.13.11",
"reselect": "4.1.7", "reselect": "4.1.8",
"universal-cookie": "4.0.4" "universal-cookie": "4.0.4"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "17.5.1", "@commitlint/cli": "17.6.5",
"@commitlint/config-angular": "17.4.4", "@commitlint/config-angular": "17.6.5",
"@edx/browserslist-config": "^1.1.1", "@edx/browserslist-config": "^1.1.1",
"@edx/frontend-build": "12.8.6", "@edx/frontend-build": "12.8.51",
"@edx/reactifex": "2.1.1", "@edx/reactifex": "2.2.0",
"@testing-library/react": "11.2.7", "@testing-library/react": "11.2.7",
"codecov": "3.8.3", "codecov": "3.8.3",
"enzyme": "3.11.0", "enzyme": "3.11.0",

View File

@@ -1,3 +1,6 @@
import { messages as headerMessages } from '@edx/frontend-component-header';
import { messages as footerMessages } from '@edx/frontend-component-footer';
import arMessages from './messages/ar.json'; import arMessages from './messages/ar.json';
import frMessages from './messages/fr.json'; import frMessages from './messages/fr.json';
import es419Messages from './messages/es_419.json'; import es419Messages from './messages/es_419.json';
@@ -11,7 +14,7 @@ import hiMessages from './messages/hi.json';
import frCAMessages from './messages/fr_CA.json'; import frCAMessages from './messages/fr_CA.json';
// no need to import en messages-- they are in the defaultMessage field // no need to import en messages-- they are in the defaultMessage field
const messages = { const appMessages = {
ar: arMessages, ar: arMessages,
'es-419': es419Messages, 'es-419': es419Messages,
fr: frMessages, fr: frMessages,
@@ -25,4 +28,8 @@ const messages = {
uk: ukMessages, uk: ukMessages,
}; };
export default messages; export default [
headerMessages,
footerMessages,
appMessages,
];

View File

@@ -34,11 +34,11 @@
"profile.formcontrols.button.saved": "تم الحفظ", "profile.formcontrols.button.saved": "تم الحفظ",
"profile.visibility.who.just.me": "أنا فقط", "profile.visibility.who.just.me": "أنا فقط",
"profile.visibility.who.everyone": "جميع من على {siteName}", "profile.visibility.who.everyone": "جميع من على {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal", "profile.learningGoal.learningGoal": "هدف التعلم",
"profile.learningGoal.options.start_career": "I want to start my career", "profile.learningGoal.options.start_career": "أريد أن أبدأ مسيرتي المهنية",
"profile.learningGoal.options.advance_career": "I want to advance my career", "profile.learningGoal.options.advance_career": "أريد أن ارتقي في مسيرتي المهنية",
"profile.learningGoal.options.learn_something_new": "I want to learn something new", "profile.learningGoal.options.learn_something_new": "أريد أن أتعلم شيئًا جديدًا",
"profile.learningGoal.options.something_else": "Something else", "profile.learningGoal.options.something_else": "شيء آخر",
"profile.name.full.name": "الاسم الكامل", "profile.name.full.name": "الاسم الكامل",
"profile.name.details": "هذا هو الاسم الذي يظهر في حسابك وفي شهاداتك", "profile.name.details": "هذا هو الاسم الذي يظهر في حسابك وفي شهاداتك",
"profile.name.empty": "إضافة الاسم", "profile.name.empty": "إضافة الاسم",
@@ -54,31 +54,32 @@
"profile.viewMyRecords": "عرض سجلّاتي", "profile.viewMyRecords": "عرض سجلّاتي",
"profile.loading": "يتم تحميل الملف الشخصي...", "profile.loading": "يتم تحميل الملف الشخصي...",
"profile.username.description": "معلومات ملفك الشخصي تظهر لك فقط. وحده اسم المستخدم الخاص بك يظهر للآخرين على {siteName}.", "profile.username.description": "معلومات ملفك الشخصي تظهر لك فقط. وحده اسم المستخدم الخاص بك يظهر للآخرين على {siteName}.",
"skills.builder.header.title": "Skills Builder", "skills.builder.header.title": "باني المهارات",
"skills.builder.header.subheading": "Let edX be your guide", "skills.builder.header.subheading": "دع (المنصة التعليمية أو edX) ان تكون دليلك",
"go.back.button": "Go Back", "skills.builder.header.title.is.medium": "edX Skills builder",
"next.step.button": "Next Step", "go.back.button": "العودة إلى الخلف",
"exit.button": "Exit", "next.step.button": "الخطوة التالية",
"select.preferences": "Select preferences", "exit.button": "خروج",
"review.results": "Review results", "select.preferences": "حدد التفضيلاتك",
"skills.builder.description": "Find the right courses and programs that help you reach your goals.", "review.results": "مراجعة النتائج",
"learning.goal.prompt": "First, tell us what you want to achieve", "skills.builder.description": "ابحث عن الدورات والبرامج المناسبة التي تساعدك في الوصول إلى أهدافك.",
"select.learning.goal": "Select a goal", "learning.goal.prompt": "أولاً، أخبرنا بما تريد تحقيقه",
"learning.goal.start_career": "I want to start my career", "select.learning.goal": "اختر هدفًا",
"learning.goal.advance_career": "I want to advance my career", "learning.goal.start_career": "أريد أن أبدأ مسيرتي المهنية",
"learning.goal.change_career": "I want to change careers", "learning.goal.advance_career": "أريد أن ارتقي في مهنتي",
"learning.goal.something.new": "I want to learn something new", "learning.goal.change_career": "اريد تغيير المهنتي",
"learning.goal.something.else": "Something else", "learning.goal.something.new": "أريد أن أتعلم شيئًا جديدًا",
"job.title.prompt": "Next, search and select your current job title", "learning.goal.something.else": "شيء آخر",
"job.title.input.placeholder.text": "Search and select a job title", "job.title.prompt": "بعد ذلك، ابحث وحدد المسمى الوظيفي الحالي الخاص بك",
"student.checkbox.prompt": "I'm a student", "job.title.input.placeholder.text": "أبحث واختار مسمى وظيفي",
"currently.looking.checkbox.prompt": "I'm currently looking for work", "student.checkbox.prompt": "أنا طالب",
"career.interest.prompt": "What careers are you interested in?", "currently.looking.checkbox.prompt": "أنا حاليا أبحث عن عمل",
"career.interest.input.placeholder.text": "Select up to 3 new job titles", "career.interest.prompt": "ما هي المهن التي تثير اهتمامك؟",
"career.interest.remove.button.alt.text": "Remove career interest: ", "career.interest.input.placeholder.text": "حدد ما يصل إلى ثلاث عناوين وظيفية جديدة",
"matches.found.success.alert": "We found skills and courses that match your preferences!", "career.interest.remove.button.alt.text": "إزالة الاهتمام الوظيفي:",
"matches.not.found.danger.alert": "We were not able to retrieve recommendations at this time. Please try again later.", "matches.found.success.alert": "وجدنا المهارات والدورات التي تناسب تفضيلاتك!",
"related.skills.heading": "Related Skills", "matches.not.found.danger.alert": "لم نتمكن من استرداد التوصيات في هذا الوقت. الرجاء معاودة المحاولة في وقت لاحق.",
"related.skills.selectable.box.label.text": "Related skills:", "related.skills.heading": "مهارات ذات الصلة",
"related.skills.selectable.box.label.text": "مهارات ذات الصلة:",
"product.recommendations.header.text": "{productType} recommendations for {jobName}" "product.recommendations.header.text": "{productType} recommendations for {jobName}"
} }

View File

@@ -56,6 +56,7 @@
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.", "profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"skills.builder.header.title": "Skills Builder", "skills.builder.header.title": "Skills Builder",
"skills.builder.header.subheading": "Let edX be your guide", "skills.builder.header.subheading": "Let edX be your guide",
"skills.builder.header.title.is.medium": "edX Skills builder",
"go.back.button": "Go Back", "go.back.button": "Go Back",
"next.step.button": "Next Step", "next.step.button": "Next Step",
"exit.button": "Exit", "exit.button": "Exit",

View File

@@ -56,6 +56,7 @@
"profile.username.description": "La información del perfil solo la visualiza usted. Solo el nombre de usuario es visible para los demás en {siteName}.", "profile.username.description": "La información del perfil solo la visualiza usted. Solo el nombre de usuario es visible para los demás en {siteName}.",
"skills.builder.header.title": "Constructor de habilidades", "skills.builder.header.title": "Constructor de habilidades",
"skills.builder.header.subheading": "Dejanos ser tu guía", "skills.builder.header.subheading": "Dejanos ser tu guía",
"skills.builder.header.title.is.medium": "edX Skills builder",
"go.back.button": "Volver Atrás", "go.back.button": "Volver Atrás",
"next.step.button": "Próximo paso", "next.step.button": "Próximo paso",
"exit.button": "Salida", "exit.button": "Salida",

View File

@@ -56,6 +56,7 @@
"profile.username.description": "Les informations de votre profil ne sont visibles que par vous. Seul votre nom d'utilisateur est visible par les autres sur {siteName}.", "profile.username.description": "Les informations de votre profil ne sont visibles que par vous. Seul votre nom d'utilisateur est visible par les autres sur {siteName}.",
"skills.builder.header.title": "Skills Builder", "skills.builder.header.title": "Skills Builder",
"skills.builder.header.subheading": "Let edX be your guide", "skills.builder.header.subheading": "Let edX be your guide",
"skills.builder.header.title.is.medium": "edX Skills builder",
"go.back.button": "Go Back", "go.back.button": "Go Back",
"next.step.button": "Next Step", "next.step.button": "Next Step",
"exit.button": "Exit", "exit.button": "Exit",

View File

@@ -56,6 +56,7 @@
"profile.username.description": "Les informations de votre profil ne sont visibles que par vous. Seul votre nom d'utilisateur est visible par les autres sur {siteName}.", "profile.username.description": "Les informations de votre profil ne sont visibles que par vous. Seul votre nom d'utilisateur est visible par les autres sur {siteName}.",
"skills.builder.header.title": "Constructeur de compétences", "skills.builder.header.title": "Constructeur de compétences",
"skills.builder.header.subheading": "Laissez EDUlib être votre guide", "skills.builder.header.subheading": "Laissez EDUlib être votre guide",
"skills.builder.header.title.is.medium": "Générateur de compétences edX",
"go.back.button": "Retour", "go.back.button": "Retour",
"next.step.button": "Prochaine étape", "next.step.button": "Prochaine étape",
"exit.button": "Sortie", "exit.button": "Sortie",

View File

@@ -56,6 +56,7 @@
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.", "profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"skills.builder.header.title": "Skills Builder", "skills.builder.header.title": "Skills Builder",
"skills.builder.header.subheading": "Let edX be your guide", "skills.builder.header.subheading": "Let edX be your guide",
"skills.builder.header.title.is.medium": "edX Skills builder",
"go.back.button": "Go Back", "go.back.button": "Go Back",
"next.step.button": "Next Step", "next.step.button": "Next Step",
"exit.button": "Exit", "exit.button": "Exit",

View File

@@ -56,6 +56,7 @@
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.", "profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"skills.builder.header.title": "Skills Builder", "skills.builder.header.title": "Skills Builder",
"skills.builder.header.subheading": "Let edX be your guide", "skills.builder.header.subheading": "Let edX be your guide",
"skills.builder.header.title.is.medium": "edX Skills builder",
"go.back.button": "Go Back", "go.back.button": "Go Back",
"next.step.button": "Next Step", "next.step.button": "Next Step",
"exit.button": "Exit", "exit.button": "Exit",

View File

@@ -56,6 +56,7 @@
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.", "profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"skills.builder.header.title": "Skills Builder", "skills.builder.header.title": "Skills Builder",
"skills.builder.header.subheading": "Let edX be your guide", "skills.builder.header.subheading": "Let edX be your guide",
"skills.builder.header.title.is.medium": "edX Skills builder",
"go.back.button": "Go Back", "go.back.button": "Go Back",
"next.step.button": "Next Step", "next.step.button": "Next Step",
"exit.button": "Exit", "exit.button": "Exit",

View File

@@ -56,6 +56,7 @@
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.", "profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"skills.builder.header.title": "Skills Builder", "skills.builder.header.title": "Skills Builder",
"skills.builder.header.subheading": "Let edX be your guide", "skills.builder.header.subheading": "Let edX be your guide",
"skills.builder.header.title.is.medium": "edX Skills builder",
"go.back.button": "Go Back", "go.back.button": "Go Back",
"next.step.button": "Next Step", "next.step.button": "Next Step",
"exit.button": "Exit", "exit.button": "Exit",

View File

@@ -8,7 +8,7 @@
"profile.certificate.organization.label": "From", "profile.certificate.organization.label": "From",
"profile.certificate.completion.date.label": "Completed on {date}", "profile.certificate.completion.date.label": "Completed on {date}",
"profile.no.certificates": "You don't have any certificates yet.", "profile.no.certificates": "You don't have any certificates yet.",
"profile.certificates.my.certificates": "My Certificates", "profile.certificates.my.certificates": "Мої сертифікати",
"profile.certificates.view.certificate": "View Certificate", "profile.certificates.view.certificate": "View Certificate",
"profile.certificates.types.verified": "Verified Certificate", "profile.certificates.types.verified": "Verified Certificate",
"profile.certificates.types.professional": "Professional Certificate", "profile.certificates.types.professional": "Professional Certificate",
@@ -56,6 +56,7 @@
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.", "profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"skills.builder.header.title": "Skills Builder", "skills.builder.header.title": "Skills Builder",
"skills.builder.header.subheading": "Let edX be your guide", "skills.builder.header.subheading": "Let edX be your guide",
"skills.builder.header.title.is.medium": "edX Skills builder",
"go.back.button": "Go Back", "go.back.button": "Go Back",
"next.step.button": "Next Step", "next.step.button": "Next Step",
"exit.button": "Exit", "exit.button": "Exit",

View File

@@ -56,6 +56,7 @@
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.", "profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"skills.builder.header.title": "Skills Builder", "skills.builder.header.title": "Skills Builder",
"skills.builder.header.subheading": "Let edX be your guide", "skills.builder.header.subheading": "Let edX be your guide",
"skills.builder.header.title.is.medium": "edX Skills builder",
"go.back.button": "Go Back", "go.back.button": "Go Back",
"next.step.button": "Next Step", "next.step.button": "Next Step",
"exit.button": "Exit", "exit.button": "Exit",

View File

@@ -16,10 +16,10 @@ import {
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import Header, { messages as headerMessages } from '@edx/frontend-component-header'; import Header from '@edx/frontend-component-header';
import Footer, { messages as footerMessages } from '@edx/frontend-component-footer'; import Footer from '@edx/frontend-component-footer';
import appMessages from './i18n'; import messages from './i18n';
import configureStore from './data/configureStore'; import configureStore from './data/configureStore';
import './index.scss'; import './index.scss';
@@ -46,11 +46,7 @@ subscribe(APP_INIT_ERROR, (error) => {
}); });
initialize({ initialize({
messages: [ messages,
appMessages,
headerMessages,
footerMessages,
],
hydrateAuthenticatedUser: true, hydrateAuthenticatedUser: true,
handlers: { handlers: {
config: () => { config: () => {
@@ -62,6 +58,7 @@ initialize({
ALGOLIA_JOBS_INDEX_NAME: process.env.ALGOLIA_JOBS_INDEX_NAME || null, ALGOLIA_JOBS_INDEX_NAME: process.env.ALGOLIA_JOBS_INDEX_NAME || null,
ALGOLIA_PRODUCT_INDEX_NAME: process.env.ALGOLIA_PRODUCT_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, 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'); }, 'App loadConfig override handler');
}, },
}, },

View File

@@ -0,0 +1,94 @@
{
"consumer": {
"name": "frontend-app-profile"
},
"interactions": [
{
"description": "a request for a user's account information",
"providerStates": [
{
"name": "I have user account information"
}
],
"request": {
"headers": {
"Accept": "application/json"
},
"method": "GET",
"path": "/api/user/v1/accounts/edx"
},
"response": {
"body": {
"accomplishments_shared": false,
"account_privacy": "private",
"activation_key": null,
"bio": null,
"country": null,
"course_certificates": null,
"date_joined": "2021-07-30T20:01:46Z",
"email": "edx@example.com",
"extended_profile": [],
"gender": null,
"goals": null,
"id": 3,
"is_active": true,
"language_proficiencies": [],
"last_login": "2023-04-10T18:28:01.771896Z",
"level_of_education": null,
"mailing_address": null,
"name": "",
"pending_name_change": null,
"phone_number": null,
"profile_image": {
"has_image": false,
"image_url_full": "http://localhost:18000/static/images/profiles/default_500.png",
"image_url_large": "http://localhost:18000/static/images/profiles/default_120.png",
"image_url_medium": "http://localhost:18000/static/images/profiles/default_50.png",
"image_url_small": "http://localhost:18000/static/images/profiles/default_30.png"
},
"requires_parental_consent": true,
"secondary_email": null,
"secondary_email_enabled": null,
"social_links": [],
"state": null,
"time_zone": null,
"username": "edx",
"verified_name": null,
"year_of_birth": null
},
"headers": {
"Content-Type": "application/json"
},
"matchingRules": {
"body": {
"$": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
}
},
"header": {}
},
"status": 200
}
}
],
"metadata": {
"pact-js": {
"version": "11.0.2"
},
"pactRust": {
"ffi": "0.4.0",
"models": "1.0.4"
},
"pactSpecification": {
"version": "3.0.0"
}
},
"provider": {
"name": "edx-platform"
}
}

View File

@@ -163,7 +163,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -241,7 +241,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -1745,7 +1745,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z" d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -2387,7 +2387,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -2544,7 +2544,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -2622,7 +2622,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -3100,7 +3100,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z" d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -3562,7 +3562,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -3719,7 +3719,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -3797,7 +3797,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -5060,7 +5060,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z" d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -5612,7 +5612,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -5723,7 +5723,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24zM3.42 2.45L2.01 3.87l2.68 2.68A11.738 11.738 0 001 11.5C2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l3.43 3.43 1.41-1.41L3.42 2.45zM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5zm2.97-5.33a2.97 2.97 0 00-2.64-2.64l2.64 2.64z" d="M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24ZM3.42 2.45 2.01 3.87l2.68 2.68A11.738 11.738 0 0 0 1 11.5C2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l3.43 3.43 1.41-1.41L3.42 2.45ZM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5Zm2.97-5.33a2.97 2.97 0 0 0-2.64-2.64l2.64 2.64Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -5784,7 +5784,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24zM3.42 2.45L2.01 3.87l2.68 2.68A11.738 11.738 0 001 11.5C2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l3.43 3.43 1.41-1.41L3.42 2.45zM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5zm2.97-5.33a2.97 2.97 0 00-2.64-2.64l2.64 2.64z" d="M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24ZM3.42 2.45 2.01 3.87l2.68 2.68A11.738 11.738 0 0 0 1 11.5C2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l3.43 3.43 1.41-1.41L3.42 2.45ZM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5Zm2.97-5.33a2.97 2.97 0 0 0-2.64-2.64l2.64 2.64Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -6001,7 +6001,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -6079,7 +6079,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -6894,7 +6894,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -7051,7 +7051,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -7129,7 +7129,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -7818,7 +7818,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z" d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -8011,7 +8011,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -8168,7 +8168,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -8246,7 +8246,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -8941,7 +8941,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z" d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -9134,7 +9134,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
@@ -10094,7 +10094,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>

View File

View File

@@ -0,0 +1,90 @@
import path from 'path';
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import { getAccountRequest } from './services';
import { getConfig, initializeMockApp, setConfig } from '@edx/frontend-platform';
// Create a 'pact' between the two applications in the integration we are testing
const provider = new PactV3({
log: path.resolve(process.cwd(), 'src/pact-logs/pact.log'),
dir: path.resolve(process.cwd(), 'src/pacts'),
consumer: 'frontend-app-profile',
provider: 'edx-platform',
});
const getAccountBody = {
"account_privacy":"private",
"profile_image":{
"has_image":false,
"image_url_full":"http://localhost:18000/static/images/profiles/default_500.png",
"image_url_large":"http://localhost:18000/static/images/profiles/default_120.png",
"image_url_medium":"http://localhost:18000/static/images/profiles/default_50.png",
"image_url_small":"http://localhost:18000/static/images/profiles/default_30.png"
},"username":"edx",
"bio":null,
"course_certificates":null,
"country":null,
"date_joined":"2021-07-30T20:01:46Z",
"language_proficiencies":[],
"level_of_education":null,
"social_links":[],
"time_zone":null,
"accomplishments_shared":false,
"name":"",
"email":"edx@example.com",
"id":3,
"verified_name":null,
"extended_profile":[],
"gender":null,
"state":null,
"goals":null,
"is_active":true,
"last_login":"2023-04-10T18:28:01.771896Z",
"mailing_address":null,
"requires_parental_consent":true,
"secondary_email":null,
"secondary_email_enabled":null,
"year_of_birth":null,
"phone_number":null,
"activation_key":null,
"pending_name_change":null
}
const EXPECTED_GET_ACCOUNT_BODY = MatchersV3.like(getAccountBody);
describe('getAccount', () => {
beforeAll(async () => {
initializeMockApp();
});
it('returns an HTTP 200 and user account information', () => {
const username = 'edx';
provider
.given("I have user account information")
.uponReceiving(`a request for a user's account information`)
.withRequest({
method: 'GET',
path: `/api/user/v1/accounts/${username}`,
headers: { Accept: 'application/json' },
})
.willRespondWith({
status: 200,
headers: { 'Content-Type': 'application/json' },
body: EXPECTED_GET_ACCOUNT_BODY
});
return provider.executeTest(async (mockserver) => {
const port = mockserver.port;
setConfig({
...getConfig(),
LMS_BASE_URL: `http://localhost:${port}`,
});
const response = await getAccountRequest(username);
expect(response.data).toEqual(getAccountBody);
})
});
});

View File

@@ -1,5 +1,5 @@
import { ensureConfig, getConfig } from '@edx/frontend-platform'; import { ensureConfig, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient as getHttpClient } from '@edx/frontend-platform/auth'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { logError } from '@edx/frontend-platform/logging'; import { logError } from '@edx/frontend-platform/logging';
import { camelCaseObject, convertKeyNames, snakeCaseObject } from '../utils'; import { camelCaseObject, convertKeyNames, snakeCaseObject } from '../utils';
@@ -21,17 +21,24 @@ function processAndThrowError(error, errorDataProcessor) {
// GET ACCOUNT // GET ACCOUNT
export async function getAccount(username) { export async function getAccount(username) {
const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`); const { data } = await getAccountRequest(username);
// Process response data // Process response data
return processAccountData(data); return processAccountData(data);
} }
export async function getAccountRequest(username) {
return await getAuthenticatedHttpClient().get(
`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`,
{ mockApiId: 'getAccount' }
);
}
// PATCH PROFILE // PATCH PROFILE
export async function patchProfile(username, params) { export async function patchProfile(username, params) {
const processedParams = snakeCaseObject(params); const processedParams = snakeCaseObject(params);
const { data } = await getHttpClient() const { data } = await getAuthenticatedHttpClient()
.patch(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`, processedParams, { .patch(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`, processedParams, {
headers: { headers: {
'Content-Type': 'application/merge-patch+json', 'Content-Type': 'application/merge-patch+json',
@@ -49,7 +56,7 @@ export async function patchProfile(username, params) {
export async function postProfilePhoto(username, formData) { export async function postProfilePhoto(username, formData) {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const { data } = await getHttpClient().post( const { data } = await getAuthenticatedHttpClient().post(
`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`, `${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`,
formData, formData,
{ {
@@ -74,7 +81,7 @@ export async function postProfilePhoto(username, formData) {
export async function deleteProfilePhoto(username) { export async function deleteProfilePhoto(username) {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const { data } = await getHttpClient().delete(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`); const { data } = await getAuthenticatedHttpClient().delete(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`);
// TODO: Someday in the future the POST photo endpoint // TODO: Someday in the future the POST photo endpoint
// will return the new values. At that time we should // will return the new values. At that time we should
@@ -87,7 +94,7 @@ export async function deleteProfilePhoto(username) {
// GET PREFERENCES // GET PREFERENCES
export async function getPreferences(username) { export async function getPreferences(username) {
const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`); const { data } = await getAuthenticatedHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`, { mockApiId: 'getPreferences'});
return camelCaseObject(data); return camelCaseObject(data);
} }
@@ -107,7 +114,7 @@ export async function patchPreferences(username, params) {
visibility_time_zone: 'visibility.time_zone', visibility_time_zone: 'visibility.time_zone',
}); });
await getHttpClient().patch(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, { await getAuthenticatedHttpClient().patch(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, {
headers: { 'Content-Type': 'application/merge-patch+json' }, headers: { 'Content-Type': 'application/merge-patch+json' },
}); });
@@ -140,7 +147,7 @@ function transformCertificateData(data) {
export async function getCourseCertificates(username) { export async function getCourseCertificates(username) {
const url = `${getConfig().LMS_BASE_URL}/api/certificates/v0/certificates/${username}/`; const url = `${getConfig().LMS_BASE_URL}/api/certificates/v0/certificates/${username}/`;
try { try {
const { data } = await getHttpClient().get(url); const { data } = await getAuthenticatedHttpClient().get(url, { mockApiId: 'getCourseCertificates' });
return transformCertificateData(data); return transformCertificateData(data);
} catch (e) { } catch (e) {
logError(e); logError(e);

View File

@@ -170,7 +170,7 @@ exports[`<SocialLinks /> calls social links with edit mode bio 1`] = `
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z" d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>

View File

@@ -1,11 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import edXLogo from '../images/edX-logo.svg'; import edXLogo from '../images/edX-logo.svg';
import messages from './messages'; import messages from './messages';
const SkillsBuilderHeader = () => { const SkillsBuilderHeader = ({ isMedium }) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
if (isMedium) {
return (
<div className="d-flex">
<h1 className="h3 mb-0 text-white">
{formatMessage(messages.skillsBuilderHeaderTitleIsMedium)}
</h1>
</div>
);
}
return ( return (
<div className="d-flex"> <div className="d-flex">
<img src={edXLogo} alt="edx-logo" className="mt-2 h-50" /> <img src={edXLogo} alt="edx-logo" className="mt-2 h-50" />
@@ -22,4 +32,8 @@ const SkillsBuilderHeader = () => {
); );
}; };
SkillsBuilderHeader.propTypes = {
isMedium: PropTypes.func.isRequired,
};
export default SkillsBuilderHeader; export default SkillsBuilderHeader;

View File

@@ -11,6 +11,11 @@ const messages = defineMessages({
defaultMessage: 'Let edX be your guide', defaultMessage: 'Let edX be your guide',
description: 'Subheading to the Skills Builder title in the header component', 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; export default messages;

View File

@@ -1,9 +1,10 @@
import React, { useState, useContext } from 'react'; import React, { useState, useContext } from 'react';
import { import {
Button, Container, Stepper, ModalDialog, Button, Container, Stepper, ModalDialog, Form, Hyperlink, useMediaQuery, breakpoints,
} from '@edx/paragon'; } from '@edx/paragon';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import { useHistory } from 'react-router'; import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { import {
STEP1, STEP2, STEP1, STEP2,
} from '../data/constants'; } from '../data/constants';
@@ -18,14 +19,36 @@ import headerImage from '../images/headerImage.png';
const SkillsBuilderModal = () => { const SkillsBuilderModal = () => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const isMedium = useMediaQuery({ maxWidth: breakpoints.medium.maxWidth });
const { state } = useContext(SkillsBuilderContext); const { state } = useContext(SkillsBuilderContext);
const { careerInterests } = state; const { currentGoal, currentJobTitle, careerInterests } = state;
const [currentStep, setCurrentStep] = useState(STEP1); const [currentStep, setCurrentStep] = useState(STEP1);
const history = useHistory(); 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 onCloseHandle = () => { const nextStepHandle = () => {
history.goBack(); setCurrentStep(STEP2);
sendActionButtonEvent('next_step');
};
const exitButtonHandle = () => {
sendActionButtonEvent('exit');
};
const closeButtonHandle = () => {
sendActionButtonEvent('close');
window.location.href = getConfig().MARKETING_SITE_SEARCH_URL;
}; };
return ( return (
@@ -35,55 +58,52 @@ const SkillsBuilderModal = () => {
size="fullscreen" size="fullscreen"
className="skills-builder-modal bg-light-200" className="skills-builder-modal bg-light-200"
isOpen isOpen
onClose={onCloseHandle} onClose={closeButtonHandle}
> >
<ModalDialog.Hero> <ModalDialog.Hero className="med-min-height">
<ModalDialog.Hero.Background className="bg-primary-500"> <ModalDialog.Hero.Background className="bg-primary-500">
<img src={headerImage} alt="" className="h-100" /> { !isMedium && <img src={headerImage} alt="" className="h-100" /> }
</ModalDialog.Hero.Background> </ModalDialog.Hero.Background>
<ModalDialog.Hero.Content> <ModalDialog.Hero.Content>
<SkillsBuilderHeader /> <SkillsBuilderHeader isMedium={isMedium} />
</ModalDialog.Hero.Content> </ModalDialog.Hero.Content>
</ModalDialog.Hero> </ModalDialog.Hero>
<Stepper.Header /> <Stepper.Header compactWidth="md" />
<ModalDialog.Body> <ModalDialog.Body>
<Container size="md" className="p-4.5"> <Container size="md" className="p-4.5">
<Stepper.Step eventKey={STEP1} title={formatMessage(messages.selectPreferences)}> <Form>
<SelectPreferences /> <Stepper.Step eventKey={STEP1} title={formatMessage(messages.selectPreferences)}>
</Stepper.Step> <SelectPreferences />
</Stepper.Step>
<Stepper.Step eventKey={STEP2} title={formatMessage(messages.reviewResults)}> <Stepper.Step eventKey={STEP2} title={formatMessage(messages.reviewResults)}>
<ViewResults /> <ViewResults />
</Stepper.Step> </Stepper.Step>
</Form>
</Container> </Container>
</ModalDialog.Body> </ModalDialog.Body>
<ModalDialog.Footer> <ModalDialog.Footer>
<Stepper.ActionRow eventKey={STEP1}> <Stepper.ActionRow eventKey={STEP1}>
<Button variant="outline-primary" onClick={onCloseHandle}>
{formatMessage(messages.goBackButton)}
</Button>
<Stepper.ActionRow.Spacer />
<Button <Button
onClick={() => setCurrentStep(STEP2)} onClick={nextStepHandle}
disabled={careerInterests.length === 0} disabled={careerInterests.length === 0}
> >
{formatMessage(messages.nextStepButton)} {formatMessage(messages.nextStepButton)}
</Button> </Button>
</Stepper.ActionRow> </Stepper.ActionRow>
<Stepper.ActionRow eventKey={STEP2}> <Stepper.ActionRow eventKey={STEP2}>
<Button <Button variant="outline-primary" onClick={() => setCurrentStep(STEP1)}>
variant="outline-primary"
onClick={() => setCurrentStep(STEP1)}
>
{formatMessage(messages.goBackButton)} {formatMessage(messages.goBackButton)}
</Button> </Button>
<Stepper.ActionRow.Spacer /> <Stepper.ActionRow.Spacer />
<Button onClick={onCloseHandle}> <Hyperlink destination={getConfig().MARKETING_SITE_SEARCH_URL}>
{formatMessage(messages.exitButton)} <Button onClick={exitButtonHandle}>
</Button> {formatMessage(messages.exitButton)}
</Button>
</Hyperlink>
</Stepper.ActionRow> </Stepper.ActionRow>
</ModalDialog.Footer> </ModalDialog.Footer>
</ModalDialog> </ModalDialog>

View File

@@ -4,6 +4,7 @@ import {
IconButton, Icon, IconButton, Icon,
} from '@edx/paragon'; } from '@edx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { Close } from '@edx/paragon/icons'; import { Close } from '@edx/paragon/icons';
import { SkillsBuilderContext } from '../../skills-builder-context'; import { SkillsBuilderContext } from '../../skills-builder-context';
import { removeCareerInterest } from '../../data/actions'; import { removeCareerInterest } from '../../data/actions';
@@ -13,6 +14,21 @@ const CareerInterestCard = ({ interest }) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { dispatch } = useContext(SkillsBuilderContext); 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 ( return (
<div className="d-flex justify-content-between align-items-center pb-2 pr-2 pl-4 rounded shadow-sm"> <div className="d-flex justify-content-between align-items-center pb-2 pr-2 pl-4 rounded shadow-sm">
<p className="pt-4"> <p className="pt-4">
@@ -22,7 +38,7 @@ const CareerInterestCard = ({ interest }) => {
iconAs={Icon} iconAs={Icon}
src={Close} src={Close}
alt={`${formatMessage(messages.removeCareerInterestButtonAltText)} ${interest}`} alt={`${formatMessage(messages.removeCareerInterestButtonAltText)} ${interest}`}
onClick={() => dispatch(removeCareerInterest(interest))} onClick={handleRemoveCareerInterest}
/> />
</div> </div>
); );

View File

@@ -1,10 +1,11 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { import {
Stack, Row, Col, Form, Stack, Row, Col, Form,
} from '@edx/paragon'; } from '@edx/paragon';
import { InstantSearch } from 'react-instantsearch-hooks-web'; import { Configure, InstantSearch } from 'react-instantsearch-hooks-web';
import JobTitleInstantSearch from './JobTitleInstantSearch'; import JobTitleInstantSearch from './JobTitleInstantSearch';
import CareerInterestCard from './CareerInterestCard'; import CareerInterestCard from './CareerInterestCard';
import { addCareerInterest } from '../../data/actions'; import { addCareerInterest } from '../../data/actions';
@@ -20,6 +21,17 @@ const CareerInterestSelect = () => {
const handleCareerInterestSelect = (value) => { const handleCareerInterestSelect = (value) => {
if (!careerInterests.includes(value) && careerInterests.length < 3) { if (!careerInterests.includes(value) && careerInterests.length < 3) {
dispatch(addCareerInterest(value)); dispatch(addCareerInterest(value));
sendTrackEvent(
'edx.skills_builder.career_interest.added',
{
app_name: 'skills_builder',
category: 'skills_builder',
learner_data: {
career_interest: value,
},
},
);
} }
}; };
@@ -30,9 +42,11 @@ const CareerInterestSelect = () => {
{formatMessage(messages.careerInterestPrompt)} {formatMessage(messages.careerInterestPrompt)}
</h4> </h4>
<InstantSearch searchClient={searchClient} indexName={getConfig().ALGOLIA_JOBS_INDEX_NAME}> <InstantSearch searchClient={searchClient} indexName={getConfig().ALGOLIA_JOBS_INDEX_NAME}>
<Configure filters="b2c_opt_in:true" />
<JobTitleInstantSearch <JobTitleInstantSearch
onSelected={handleCareerInterestSelect} onSelected={handleCareerInterestSelect}
placeholder={formatMessage(messages.careerInterestInputPlaceholderText)} placeholder={formatMessage(messages.careerInterestInputPlaceholderText)}
data-testid="career-interest-select"
/> />
</InstantSearch> </InstantSearch>
</Form.Label> </Form.Label>

View File

@@ -3,6 +3,7 @@ import {
Form, Form,
} from '@edx/paragon'; } from '@edx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { setGoal } from '../../data/actions'; import { setGoal } from '../../data/actions';
import { SkillsBuilderContext } from '../../skills-builder-context'; import { SkillsBuilderContext } from '../../skills-builder-context';
import messages from './messages'; import messages from './messages';
@@ -12,6 +13,22 @@ const GoalDropdown = () => {
const { state, dispatch } = useContext(SkillsBuilderContext); const { state, dispatch } = useContext(SkillsBuilderContext);
const { currentGoal } = state; 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 ( return (
<Form.Group> <Form.Group>
<Form.Label> <Form.Label>
@@ -22,10 +39,10 @@ const GoalDropdown = () => {
<Form.Control <Form.Control
as="select" as="select"
value={currentGoal} value={currentGoal}
onChange={(e) => dispatch(setGoal(e.target.value))} onChange={handleGoalSelect}
data-testid="goal-select-dropdown" data-testid="goal-select-dropdown"
> >
<option value="">{formatMessage(messages.selectLearningGoal)}</option> <option value="" disabled={currentGoal}>{formatMessage(messages.selectLearningGoal)}</option>
<option>{formatMessage(messages.learningGoalStartCareer)}</option> <option>{formatMessage(messages.learningGoalStartCareer)}</option>
<option>{formatMessage(messages.learningGoalAdvanceCareer)}</option> <option>{formatMessage(messages.learningGoalAdvanceCareer)}</option>
<option>{formatMessage(messages.learningGoalChangeCareer)}</option> <option>{formatMessage(messages.learningGoalChangeCareer)}</option>

View File

@@ -4,6 +4,7 @@ import {
Form, Stack, Form, Stack,
} from '@edx/paragon'; } from '@edx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { InstantSearch } from 'react-instantsearch-hooks-web'; import { InstantSearch } from 'react-instantsearch-hooks-web';
import { setCurrentJobTitle } from '../../data/actions'; import { setCurrentJobTitle } from '../../data/actions';
import { SkillsBuilderContext } from '../../skills-builder-context'; import { SkillsBuilderContext } from '../../skills-builder-context';
@@ -12,16 +13,37 @@ import messages from './messages';
const JobTitleSelect = () => { const JobTitleSelect = () => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { dispatch, algolia } = useContext(SkillsBuilderContext); const { state, dispatch, algolia } = useContext(SkillsBuilderContext);
const { searchClient } = algolia; const { searchClient } = algolia;
const { currentJobTitle } = state;
const handleCurrentJobTitleSelect = (value) => { const handleCurrentJobTitleSelect = (value) => {
dispatch(setCurrentJobTitle(value)); dispatch(setCurrentJobTitle(value));
sendTrackEvent(
'edx.skills_builder.current_job.select',
{
app_name: 'skills_builder',
category: 'skills_builder',
learner_data: {
current_job_title: value,
},
},
);
}; };
// Below implementation sets the job title to "Student" or "Looking for work" — this overwrites any previous selection const handleCheckboxChange = (e) => {
// This will need to be revisited when we decide what to do with this data const { value } = e.target;
const handleCheckboxChange = (e) => dispatch(setCurrentJobTitle(e.target.value)); // 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 ( return (
<Stack> <Stack>
@@ -33,6 +55,7 @@ const JobTitleSelect = () => {
<JobTitleInstantSearch <JobTitleInstantSearch
onSelected={handleCurrentJobTitleSelect} onSelected={handleCurrentJobTitleSelect}
placeholder={formatMessage(messages.jobTitleInputPlaceholderText)} placeholder={formatMessage(messages.jobTitleInputPlaceholderText)}
data-testid="job-title-select"
/> />
</InstantSearch> </InstantSearch>
</Form.Label> </Form.Label>
@@ -41,10 +64,10 @@ const JobTitleSelect = () => {
name="other-occupations" name="other-occupations"
onChange={handleCheckboxChange} onChange={handleCheckboxChange}
> >
<Form.Checkbox value="Student"> <Form.Checkbox value="student">
{formatMessage(messages.studentCheckboxPrompt)} {formatMessage(messages.studentCheckboxPrompt)}
</Form.Checkbox> </Form.Checkbox>
<Form.Checkbox value="Looking for work"> <Form.Checkbox value="looking_for_work">
{formatMessage(messages.currentlyLookingCheckboxPrompt)} {formatMessage(messages.currentlyLookingCheckboxPrompt)}
</Form.Checkbox> </Form.Checkbox>
</Form.CheckboxSet> </Form.CheckboxSet>

View File

@@ -27,7 +27,7 @@ const SelectPreferences = () => {
<JobTitleSelect /> <JobTitleSelect />
)} )}
{currentJobTitle && ( {currentGoal && currentJobTitle && (
<CareerInterestSelect /> <CareerInterestSelect />
)} )}
</Stack> </Stack>

View File

@@ -2,8 +2,13 @@ import {
screen, render, cleanup, fireEvent, screen, render, cleanup, fireEvent,
} from '@testing-library/react'; } from '@testing-library/react';
import { mergeConfig } from '@edx/frontend-platform'; import { mergeConfig } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { SkillsBuilderWrapperWithContext, dispatchMock, contextValue } from '../../../test/setupSkillsBuilder'; import { SkillsBuilderWrapperWithContext, dispatchMock, contextValue } from '../../../test/setupSkillsBuilder';
jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
}));
describe('select-preferences', () => { describe('select-preferences', () => {
beforeAll(() => { beforeAll(() => {
mergeConfig({ mergeConfig({
@@ -29,8 +34,13 @@ describe('select-preferences', () => {
payload: 'I want to advance my career', payload: 'I want to advance my career',
type: 'SET_GOAL', type: 'SET_GOAL',
}; };
const expectedStudent = {
payload: 'student',
type: 'SET_CURRENT_JOB_TITLE',
};
const expectedJobTitle = { const expectedJobTitle = {
payload: 'Student', payload: 'Prospector',
type: 'SET_CURRENT_JOB_TITLE', type: 'SET_CURRENT_JOB_TITLE',
}; };
@@ -40,9 +50,41 @@ describe('select-preferences', () => {
const checkbox = screen.getByRole('checkbox', { name: 'I\'m a student' }); const checkbox = screen.getByRole('checkbox', { name: 'I\'m a student' });
fireEvent.click(checkbox); 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(screen.getByText('Next, search and select your current job title')).toBeTruthy();
expect(dispatchMock).toHaveBeenCalledWith(expectedGoal); expect(dispatchMock).toHaveBeenCalledWith(expectedGoal);
expect(dispatchMock).toHaveBeenCalledWith(expectedStudent);
expect(dispatchMock).toHaveBeenCalledWith(expectedJobTitle); 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', () => { it('should render the third prompt if a current job title is selected', () => {
@@ -70,14 +112,33 @@ describe('select-preferences', () => {
...contextValue.state, ...contextValue.state,
currentGoal: 'I want to start my career', currentGoal: 'I want to start my career',
currentJobTitle: 'Goblin Lackey', currentJobTitle: 'Goblin Lackey',
careerInterests: ['Prospector', 'Mirror Breaker', 'Bombardment'], careerInterests: ['Prospector'],
}, },
}, },
), ),
); );
expect(screen.getByText('Prospector')).toBeTruthy(); expect(screen.getByText('Prospector')).toBeTruthy();
expect(screen.getByText('Mirror Breaker')).toBeTruthy();
expect(screen.getByText('Bombardment')).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',
},
);
}); });
}); });
@@ -104,6 +165,16 @@ describe('select-preferences', () => {
fireEvent.click(screen.getByLabelText('Remove career interest: Prospector')); fireEvent.click(screen.getByLabelText('Remove career interest: Prospector'));
expect(dispatchMock).toHaveBeenCalledWith(expected); 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',
},
},
);
}); });
}); });
}); });

View File

@@ -1,5 +1,16 @@
.skills-builder-modal { .skills-builder-modal {
button[aria-label="Close"][type="button"]{ button[aria-label="Close"][type="button"]{
color: #ffffff; color: $white;
}
}
$breakpoint-medium: 992px;
@media (max-width: $breakpoint-medium) {
.med-min-height {
min-height: map-get($spacers, 6);
}
.pgn__modal-close-container {
top: 1rem;
} }
} }

View File

@@ -1,13 +1,18 @@
import React from 'react'; import React from 'react';
import { CardCarousel } from '@edx/paragon'; import { CardCarousel } from '@edx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import RecommendationCard from './RecommendationCard'; import RecommendationCard from './RecommendationCard';
import messages from './messages'; import messages from './messages';
const CarouselStack = ({ selectedRecommendations }) => { const CarouselStack = ({ selectedRecommendations }) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { name: jobName, recommendations } = selectedRecommendations; const { id: jobId, name: jobName, recommendations } = selectedRecommendations;
const productTypeNames = Object.keys(recommendations); const productTypeNames = Object.keys(recommendations);
const courseKeys = recommendations.course?.map(rec => ({
title: rec.title,
courserun_key: rec.active_run_key,
}));
const normalizeProductTypeName = (productType) => { const normalizeProductTypeName = (productType) => {
// If the productType is more than one word (i.e. boot_camp) // If the productType is more than one word (i.e. boot_camp)
@@ -35,6 +40,24 @@ const CarouselStack = ({ selectedRecommendations }) => {
</h3> </h3>
); );
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 ( return (
productTypeNames.map(productType => ( productTypeNames.map(productType => (
<CardCarousel <CardCarousel
@@ -43,7 +66,12 @@ const CarouselStack = ({ selectedRecommendations }) => {
title={renderCarouselTitle(productType)} title={renderCarouselTitle(productType)}
> >
{recommendations[productType].map(rec => ( {recommendations[productType].map(rec => (
<RecommendationCard rec={rec} key={rec.uuid} /> <RecommendationCard
key={rec.uuid}
handleCourseCardClick={handleCourseCardClick}
rec={rec}
productType={productType}
/>
))} ))}
</CardCarousel> </CardCarousel>
))); )));

View File

@@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import { Card, Chip, Hyperlink } from '@edx/paragon'; import { Card, Chip, Hyperlink } from '@edx/paragon';
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cardImageCapFallbackSrc from '../../images/card-imagecap-fallback.png'; import cardImageCapFallbackSrc from '../../images/card-imagecap-fallback.png';
const RecommendationCard = ({ rec }) => { const RecommendationCard = ({ rec, productType, handleCourseCardClick }) => {
const { const {
card_image_url: cardImageUrl, card_image_url: cardImageUrl,
marketing_url: marketingUrl, marketing_url: marketingUrl,
active_run_key: courseKey,
owners, owners,
partner, partner,
title, title,
@@ -17,12 +17,15 @@ const RecommendationCard = ({ rec }) => {
return ( return (
<Hyperlink destination={marketingUrl} target="_blank" showLaunchIcon={false}> <Hyperlink destination={marketingUrl} target="_blank" showLaunchIcon={false}>
<Card className={classNames('carousel-card')}> <Card
className="carousel-card"
onClick={() => handleCourseCardClick(courseKey, productType)}
>
<Card.ImageCap <Card.ImageCap
src={cardImageUrl} src={cardImageUrl}
logoSrc={logoImageUrl} logoSrc={logoImageUrl}
fallackSrc={cardImageCapFallbackSrc} fallbackSrc={cardImageCapFallbackSrc}
fallBackLogo={cardImageCapFallbackSrc} fallbackLogoSrc={cardImageCapFallbackSrc}
/> />
<Card.Header title={title} /> <Card.Header title={title} />
<Card.Section> <Card.Section>
@@ -48,7 +51,10 @@ RecommendationCard.propTypes = {
key: PropTypes.string, key: PropTypes.string,
logoImageUrl: PropTypes.string, logoImageUrl: PropTypes.string,
})), })),
active_run_key: PropTypes.string.isRequired,
}).isRequired, }).isRequired,
productType: PropTypes.string.isRequired,
handleCourseCardClick: PropTypes.func.isRequired,
}; };
export default RecommendationCard; export default RecommendationCard;

View File

@@ -1,7 +1,10 @@
import React, { useContext, useEffect, useState } from 'react'; import React, {
useContext, useEffect, useState,
} from 'react';
import { import {
Stack, Row, Alert, Spinner, Stack, Row, Alert, Spinner,
} from '@edx/paragon'; } from '@edx/paragon';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import { CheckCircle, ErrorOutline } from '@edx/paragon/icons'; import { CheckCircle, ErrorOutline } from '@edx/paragon/icons';
import { SkillsBuilderContext } from '../../skills-builder-context'; import { SkillsBuilderContext } from '../../skills-builder-context';
@@ -55,9 +58,24 @@ const ViewResults = () => {
})); }));
setJobSkillsList(jobInfo); setJobSkillsList(jobInfo);
setSelectedJobTitle(jobInfo[0].name); setSelectedJobTitle(results[0].name);
setProductRecommendations(results); setProductRecommendations(results);
setIsLoading(false); 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,
});
}; };
getRecommendations() getRecommendations()
.catch(() => { .catch(() => {
@@ -70,6 +88,35 @@ const ViewResults = () => {
setSelectedRecommendations(productRecommendations.find(rec => rec.name === selectedJobTitle)); setSelectedRecommendations(productRecommendations.find(rec => rec.name === selectedJobTitle));
}, [productRecommendations, 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) { if (fetchError) {
return ( return (
<Alert <Alert
@@ -106,7 +153,7 @@ const ViewResults = () => {
<RelatedSkillsSelectableBoxSet <RelatedSkillsSelectableBoxSet
jobSkillsList={jobSkillsList} jobSkillsList={jobSkillsList}
selectedJobTitle={selectedJobTitle} selectedJobTitle={selectedJobTitle}
onChange={(e) => setSelectedJobTitle(e.target.value)} onChange={handleJobTitleChange}
/> />
<CarouselStack selectedRecommendations={selectedRecommendations} /> <CarouselStack selectedRecommendations={selectedRecommendations} />

View File

@@ -2,9 +2,14 @@ import {
screen, render, cleanup, fireEvent, act, screen, render, cleanup, fireEvent, act,
} from '@testing-library/react'; } from '@testing-library/react';
import { mergeConfig } from '@edx/frontend-platform'; import { mergeConfig } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { SkillsBuilderWrapperWithContext, contextValue } from '../../../test/setupSkillsBuilder'; import { SkillsBuilderWrapperWithContext, contextValue } from '../../../test/setupSkillsBuilder';
import { getProductRecommendations } from '../../../utils/search'; import { getProductRecommendations } from '../../../utils/search';
jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
}));
const renderSkillsBuilderWrapper = ( const renderSkillsBuilderWrapper = (
value = { value = {
...contextValue, ...contextValue,
@@ -37,22 +42,117 @@ describe('view-results', () => {
}); });
}); });
it('should render a <JobSillsSelectableBox> for each career interest the learner has submitted', async () => { afterEach(() => {
jest.clearAllMocks();
});
it('should render a <JobSillsSelectableBox> for each career interest the learner has submitted', () => {
expect(screen.getByText('Prospector')).toBeTruthy(); expect(screen.getByText('Prospector')).toBeTruthy();
expect(screen.getByText('Mirror Breaker')).toBeTruthy(); expect(screen.getByText('Mirror Breaker')).toBeTruthy();
const chipComponents = document.querySelectorAll('.pgn__chip'); const chipComponents = document.querySelectorAll('.pgn__chip');
expect(chipComponents[0].textContent).toEqual('finding shiny things'); expect(chipComponents[0].textContent).toEqual('finding shiny things');
expect(chipComponents[1].textContent).toEqual('mining'); 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 <Card> components', async () => { it('renders a carousel of <Card> components', () => {
expect(screen.getByText('Course recommendations for Prospector')).toBeTruthy(); expect(screen.getByText('Course recommendations for Prospector')).toBeTruthy();
}); });
it('changes the recommendations based on the selected job title', () => { it('changes the recommendations based on the selected job title', () => {
fireEvent.click(screen.getByRole('radio', { name: 'Mirror Breaker' })); fireEvent.click(screen.getByRole('radio', { name: 'Mirror Breaker' }));
expect(screen.getByText('Course recommendations for Mirror Breaker')).toBeTruthy(); 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',
},
],
},
},
);
}); });
}); });

View File

@@ -2,11 +2,11 @@ export const mockData = {
hits: [ hits: [
{ {
id: 0, id: 0,
name: 'Text File Engineer' name: 'Prospector'
}, },
{ {
id: 1, id: 1,
name: 'Screen Viewer' name: 'Mirror Breaker'
}, },
], ],
searchJobs: [ searchJobs: [
@@ -44,6 +44,7 @@ export const mockData = {
partner: ['edx'], partner: ['edx'],
card_image_url: 'https://thisIsAUrl.ForAnImage.01.jpeg', card_image_url: 'https://thisIsAUrl.ForAnImage.01.jpeg',
marketing_url: 'https://thisIsAUrl.ForTheRecommendedContent.01.com', marketing_url: 'https://thisIsAUrl.ForTheRecommendedContent.01.com',
active_run_key: 'MONS101',
owners: [ owners: [
{ {
logoImageUrl: 'https://thisIsAUrl.ForALogoImage.01.jpeg', logoImageUrl: 'https://thisIsAUrl.ForALogoImage.01.jpeg',
@@ -56,6 +57,7 @@ export const mockData = {
partner: ['edx'], partner: ['edx'],
card_image_url: 'https://thisIsAUrl.ForAnImage.02.jpeg', card_image_url: 'https://thisIsAUrl.ForAnImage.02.jpeg',
marketing_url: 'https://thisIsAUrl.ForTheRecommendedContent.02.com', marketing_url: 'https://thisIsAUrl.ForTheRecommendedContent.02.com',
active_run_key: 'WAR101',
owners: [ owners: [
{ {
logoImageUrl: 'https://thisIsAUrl.ForALogoImage.02.jpeg', logoImageUrl: 'https://thisIsAUrl.ForALogoImage.02.jpeg',

View File

@@ -11,6 +11,7 @@ jest.mock('@edx/frontend-platform/logging');
jest.mock('react-instantsearch-hooks-web', () => ({ jest.mock('react-instantsearch-hooks-web', () => ({
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
InstantSearch: ({ children }) => (<div>{children}</div>), InstantSearch: ({ children }) => (<div>{children}</div>),
Configure: jest.fn(() => (null)),
useSearchBox: jest.fn(() => ({ refine: jest.fn() })), useSearchBox: jest.fn(() => ({ refine: jest.fn() })),
useHits: jest.fn(() => ({ hits: mockData.hits })), useHits: jest.fn(() => ({ hits: mockData.hits })),
})); }));

View File

@@ -96,7 +96,7 @@ export const getProductRecommendations = async (productIndex, productType, skill
const formattedSkillNames = formatFacetFilterData('skills.skill', skills); const formattedSkillNames = formatFacetFilterData('skills.skill', skills);
try { try {
const { hits } = await productIndex.search('', { const { hits } = await productIndex.search('', {
filters: `product: "${productType}"`, filters: `product: "${productType}" AND language: "English"`,
facetFilters: [ facetFilters: [
formattedSkillNames, formattedSkillNames,
], ],

View File

@@ -55,7 +55,7 @@ describe('Algolias utility function', () => {
it('getProductRecommendations() queries Algolia with the expected search parameters', async () => { it('getProductRecommendations() queries Algolia with the expected search parameters', async () => {
const expectedSearchParameters = { const expectedSearchParameters = {
filters: 'product: "Course"', filters: 'product: "Course" AND language: "English"',
facetFilters: [ facetFilters: [
['skills.skill:Sword Lobbing'], ['skills.skill:Sword Lobbing'],
], ],