feat: add utility function to retrieve product recommendations based on job skills

[APER-2262]

- add a utility function to retrieve product recommendations based on skills from a job a learner is interested in
- add additional tests and coverage around new utility functions in `search.jsx`
This commit is contained in:
Justin Hynes
2023-02-23 08:41:34 -05:00
parent 2d05de92af
commit a6086fd4bf
2 changed files with 108 additions and 24 deletions

View File

@@ -41,44 +41,70 @@ export const useAlgoliaSearch = () => {
};
/*
* Utility function used to reformat incoming job names to match the syntax Algolia expects when querying index data.
* Utility function used to format a list of data so it matches syntax Algolia expects.
*
* @param {Array[String]} jobNames - A list of job names a learner is interested in
* @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 formattedJobNames - The transformed array of job names
* @return {Array[String]} formattedData - The transformed array of data to search prepended with the facet filter type
*/
export function formatJobNames(jobNames) {
const formattedJobNames = [];
if (jobNames) {
jobNames.forEach(job => formattedJobNames.push(`name:${job}`));
export function formatFacetFilterData(facetFilterType, data) {
const formattedData = [];
if (data) {
data.forEach(item => formattedData.push(`${facetFilterType}:${item}`));
}
return formattedJobNames;
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 job taxonomy data. Used to retrieve job metadata that a
* learner is interested in.
* @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 Job information retrieved from Algolia
* @return {Array[Object]} results - Job information retrieved from Algolia
*/
export const searchJobs = async (jobSearchIndex, jobNames) => {
let results = null;
const formattedJobNames = formatJobNames(jobNames);
export const searchJobs = async (jobIndex, jobNames) => {
const formattedJobNames = formatFacetFilterData('name', jobNames);
try {
const { hits } = await jobSearchIndex.search('', {
const { hits } = await jobIndex.search('', {
facetFilters: [
formattedJobNames,
],
});
results = hits;
return hits;
} catch (error) {
logError(error);
results = [];
}
return results;
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}`,
facetFilters: [
formattedSkillNames,
],
});
return hits;
} catch (error) {
logError(error);
}
return [];
};

View File

@@ -1,16 +1,74 @@
import { formatJobNames, searchJobs } from '../search';
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', () => {
it('formatJobNames() should return a new array with data formatted as expected', () => {
const jobNameArray = ['Organic Farmer'];
const result = formatJobNames(jobNameArray);
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, ['name:Organic Farmer']);
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',
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([]);
});
});