diff --git a/src/common/helper.js b/src/common/helper.js index a599fb65..2f01161a 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -221,9 +221,15 @@ esIndexPropertyMapping[config.get('esConfig.ES_INDEX_RESOURCE_BOOKING')] = { updatedBy: { type: 'keyword' } } esIndexPropertyMapping[config.get('esConfig.ES_INDEX_ROLE')] = { - name: { type: 'keyword' }, + name: { + type: 'keyword', + normalizer: 'lowercaseNormalizer' + }, description: { type: 'keyword' }, - listOfSkills: { type: 'keyword' }, + listOfSkills: { + type: 'keyword', + normalizer: 'lowercaseNormalizer' + }, rates: { properties: { global: { type: 'integer' }, @@ -1199,6 +1205,24 @@ async function getTopcoderSkills (criteria) { } } +/** + * Function to search and retrive all skills from v5/skills + * - only returns skills from Topcoder Skills Provider defined by `TOPCODER_SKILL_PROVIDER_ID` + * + * @param {Object} criteria the search criteria + * @returns the request result + */ +async function getAllTopcoderSkills (criteria) { + const skills = await getTopcoderSkills(_.assign(criteria, { page: 1, perPage: 100 })) + while (skills.page * skills.perPage <= skills.total) { + const newSkills = await getTopcoderSkills(_.assign(criteria, { page: skills.page + 1, perPage: 100 })) + skills.result = [...skills.result, ...newSkills.result] + skills.page = newSkills.page + skills.total = newSkills.total + } + return skills.result +} + /** * Function to get skill by id * @param {String} skillId the skill Id @@ -1745,16 +1769,15 @@ async function substituteStringByObject (string, object) { return string } - /** * Get tags from tagging service * @param {String} description The challenge description * @returns {Array} array of tags */ async function getTags (description) { - const data = { text: description, extract_confidence: false} - const type = "emsi/internal_no_refresh" - const url = `${config.TC_API}/contest-tagging/${type}`; + const data = { text: description, extract_confidence: false } + const type = 'emsi/internal_no_refresh' + const url = `${config.TC_API}/contest-tagging/${type}` const res = await request .post(url) .set('Accept', 'application/json') @@ -1762,12 +1785,11 @@ async function getTags (description) { localLogger.debug({ context: 'getTags', - message: `response body: ${JSON.stringify(res.body)}`, - }); - return _.get(res, 'body'); + message: `response body: ${JSON.stringify(res.body)}` + }) + return _.get(res, 'body') } - /** * @param {Object} currentUser the user performing the action * @param {Object} data title of project and any other info @@ -1819,6 +1841,7 @@ module.exports = { getMembers, getProjectById, getTopcoderSkills, + getAllTopcoderSkills, getSkillById, ensureJobById, ensureResourceBookingById, diff --git a/src/services/RoleService.js b/src/services/RoleService.js index 19006f67..bbd2c141 100644 --- a/src/services/RoleService.js +++ b/src/services/RoleService.js @@ -32,18 +32,18 @@ async function _checkUserPermissionForWriteDeleteRole (currentUser) { * @returns {undefined} */ async function _cleanAndValidateSkillNames (skills) { - // remove duplicates, leading and trailing whitespaces, remove empties and convert to lowercase. - const cleanedSkills = _.uniq(_.filter(_.map(skills, skill => _.toLower(_.trim(skill))), skill => !_.isEmpty(skill))) + // remove duplicates, leading and trailing whitespaces, empties. + const cleanedSkills = _.uniq(_.filter(_.map(skills, skill => _.trim(skill)), skill => !_.isEmpty(skill))) if (cleanedSkills.length > 0) { // search skills if they are exists - const { result } = await helper.getTopcoderSkills({ name: _.join(cleanedSkills, ',') }) + const result = await helper.getAllTopcoderSkills({ name: _.join(cleanedSkills, ',') }) const skillNames = _.map(result, 'name') // find skills that not valid - const unValidSkills = _.differenceWith(cleanedSkills, skillNames, (a, b) => _.toLower(a) === _.toLower(b)) + const unValidSkills = _.differenceBy(cleanedSkills, skillNames, _.toLower) if (unValidSkills.length > 0) { throw new errors.BadRequestError(`skills: "${unValidSkills}" are not valid`) } - return cleanedSkills + return _.intersectionBy(skillNames, cleanedSkills, _.toLower) } else { return null } @@ -232,7 +232,7 @@ deleteRole.schema = Joi.object().keys({ */ async function searchRoles (currentUser, criteria) { // clean skill names and convert into an array - criteria.skillsList = _.filter(_.map(_.split(_.trim(criteria.skillsList), ','), skill => _.toLower(_.trim(skill))), skill => !_.isEmpty(skill)) + criteria.skillsList = _.filter(_.map(_.split(criteria.skillsList, ','), skill => _.trim(skill)), skill => !_.isEmpty(skill)) try { const esQuery = { index: config.get('esConfig.ES_INDEX_ROLE'), @@ -274,7 +274,9 @@ async function searchRoles (currentUser, criteria) { const filter = { [Op.and]: [] } // Apply skill name filters. listOfSkills array should include all skills provided in criteria. if (criteria.skillsList) { - filter[Op.and].push({ listOfSkills: { [Op.contains]: criteria.skillsList } }) + _.each(criteria.skillsList, skill => { + filter[Op.and].push(models.Sequelize.literal(`LOWER('${skill}') in (SELECT lower(x) FROM unnest("list_of_skills"::text[]) x)`)) + }) } // Apply name filter, allow partial match and ignore case if (criteria.keyword) {