Skip to content

Add skillsMatch to sendRoleSearchRequest response. #366

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions data/demo-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -7258,10 +7258,30 @@
}
],
"Role": [
{
"id": "f5e01b7c-466f-45c8-989c-16ff831d7e59",
"name": "Custom",
"description": null,
"listOfSkills": null,
"rates": [{
"global": 1200,
"offShore": 1200,
"inCountry": 1200
}],
"numberOfMembers": null,
"numberOfMembersAvailable": null,
"imageUrl": null,
"timeToCandidate": null,
"timeToInterview": null,
"createdBy": "57646ff9-1cd3-4d3c-88ba-eb09a395366c",
"updatedBy": null,
"createdAt": "2021-05-27T21:43:08.201Z",
"updatedAt": "2021-05-27T21:43:08.201Z"
},
{
"id": "c145247d-5757-463d-9317-ff9e7026d403",
"name": "Angular Developer",
"description": "Angular is an open-source, client-side framework based on TypeScript and designed for building web applications.",
"description": "* Writes tested and documented JavaScript, HTML and CSS\n* Makes design and technical decisions for AngularJS projects\n* Develops application code and unit test in the AngularJS, Rest Web Services and Java technologies",
"listOfSkills": [
"database",
"winforms",
Expand Down Expand Up @@ -7293,7 +7313,7 @@
{
"id": "d7ff0289-d3ea-44d8-b39a-53bba5b5b309",
"name": "Dev Ops Engineer",
"description": "A DevOps engineer introduces processes, tools, and methodologies to balance needs throughout the software development life cycle, from coding and deployment, to maintenance and updates.",
"description": "* Introduces processes, tools, and methodologies\n* Balances needs throughout the software development life cycle\n* Configures server images, optimizes task performance in correspondence with engineers",
"listOfSkills": [
"dropwizard",
"nginx",
Expand Down Expand Up @@ -7337,7 +7357,7 @@
{
"id": "e7b7e818-40d4-4102-b486-09bdd21400b8",
"name": "Salesforce Developer",
"description": "A Salesforce developer is a programmer who builds Salesforce applications across various PaaS (Platform as a Service) platforms.",
"description": "* Meets with project managers to determine CRM needs\n* Develops customized solutions within the Salesforce platform\n* Designs, codes, and implements Salesforce applications\n* Creates timelines and development goals\n* Tests the stability and functionality of the application\n* Troubleshoots and fixes bugs\n* Writes documents and provides technical training for Salesforce Staff\n* Maintains the security and integrity of the application software",
"listOfSkills": [
"docker",
".net",
Expand Down
6 changes: 3 additions & 3 deletions docs/Topcoder-bookings-api.postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -19627,7 +19627,7 @@
"response": []
},
{
"name": "create role Niche",
"name": "create role Custom",
"event": [
{
"listen": "test",
Expand All @@ -19652,7 +19652,7 @@
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"Niche\",\n \"rates\": [\n {\n \"global\": 10,\n \"inCountry\": 10,\n \"offShore\": 10\n }\n ]\n}",
"raw": "{\n \"name\": \"Custom\",\n \"rates\": [\n {\n \"global\": 1200,\n \"inCountry\": 1200,\n \"offShore\": 1200\n }\n ]\n}",
"options": {
"raw": {
"language": "json"
Expand Down Expand Up @@ -20424,7 +20424,7 @@
"pm.test('Status code is 200', function () {\r",
" pm.response.to.have.status(200);\r",
" const response = pm.response.json()\r",
" pm.expect(response.name).to.eq(\"Niche\")\r",
" pm.expect(response.name).to.eq(\"Custom\")\r",
"});"
],
"type": "text/javascript"
Expand Down
5 changes: 5 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5505,6 +5505,11 @@ components:
isExternalMember:
type: boolean
description: "Is the user external member"
skillsMatch:
type: number
format: float
description: "Rate at which searched skills match the given role"
example: 0.75
SubmitTeamRequestBody:
properties:
teamName:
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/RoleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const service = require('../services/RoleService')
* @param res the response
*/
async function getRole (req, res) {
res.send(await service.getRole(req.authUser, req.params.id, req.query.fromDb))
res.send(await service.getRole(req.params.id, req.query.fromDb))
}

/**
Expand Down Expand Up @@ -47,7 +47,7 @@ async function deleteRole (req, res) {
* @param res the response
*/
async function searchRoles (req, res) {
res.send(await service.searchRoles(req.authUser, req.query))
res.send(await service.searchRoles(req.query))
}

module.exports = {
Expand Down
4 changes: 0 additions & 4 deletions src/routes/RoleRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,12 @@ module.exports = {
get: {
controller: 'RoleController',
method: 'searchRoles',
auth: 'jwt',
scopes: [constants.Scopes.READ_ROLE, constants.Scopes.ALL_ROLE]
}
},
'/taas-roles/:id': {
get: {
controller: 'RoleController',
method: 'getRole',
auth: 'jwt',
scopes: [constants.Scopes.READ_ROLE, constants.Scopes.ALL_ROLE]
},
patch: {
controller: 'RoleController',
Expand Down
4 changes: 0 additions & 4 deletions src/routes/TeamRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ module.exports = {
get: {
controller: 'TeamController',
method: 'searchSkills',
auth: 'jwt',
scopes: [constants.Scopes.READ_TAAS_TEAM]
}
},
'/taas-teams/me': {
Expand Down Expand Up @@ -94,8 +92,6 @@ module.exports = {
post: {
controller: 'TeamController',
method: 'roleSearchRequest',
auth: 'jwt',
scopes: [constants.Scopes.CREATE_ROLE_SEARCH_REQUEST]
}
},
'/taas-teams/submitTeamRequest': {
Expand Down
6 changes: 2 additions & 4 deletions src/services/RoleService.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async function _checkIfSameNamedRoleExists (roleName) {
* @param {Boolean} fromDb flag if query db for data or not
* @returns {Object} the role
*/
async function getRole (currentUser, id, fromDb = false) {
async function getRole (id, fromDb = false) {
if (!fromDb) {
try {
const role = await esClient.get({
Expand All @@ -95,7 +95,6 @@ async function getRole (currentUser, id, fromDb = false) {
}

getRole.schema = Joi.object().keys({
currentUser: Joi.object().required(),
id: Joi.string().uuid().required(),
fromDb: Joi.boolean()
}).required()
Expand Down Expand Up @@ -230,7 +229,7 @@ deleteRole.schema = Joi.object().keys({
* @param {Object} criteria the search criteria
* @returns {Object} the search result
*/
async function searchRoles (currentUser, criteria) {
async function searchRoles (criteria) {
// clean skill names and convert into an array
criteria.skillsList = _.filter(_.map(_.split(criteria.skillsList, ','), skill => _.trim(skill)), skill => !_.isEmpty(skill))
try {
Expand Down Expand Up @@ -291,7 +290,6 @@ async function searchRoles (currentUser, criteria) {
}

searchRoles.schema = Joi.object().keys({
currentUser: Joi.object().required(),
criteria: Joi.object().keys({
skillsList: Joi.string(),
keyword: Joi.string()
Expand Down
25 changes: 16 additions & 9 deletions src/services/TeamService.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const HttpStatus = require('http-status-codes')
const { Op } = require('sequelize')
const models = require('../models')
const stopWords = require('../../data/stopWords.json')
const { getAuditM2Muser } = require('../common/helper')
const Role = models.Role
const RoleSearchRequest = models.RoleSearchRequest
const topcoderSkills = {}
Expand Down Expand Up @@ -750,11 +751,16 @@ getMe.schema = Joi.object()
* @returns {Object} the created project
*/
async function roleSearchRequest (currentUser, data) {
// if currentUser is undefined then set to machine
if (_.isUndefined(currentUser)) {
currentUser = getAuditM2Muser()
}
let role
// if roleId is provided then find role with given id.
if (!_.isUndefined(data.roleId)) {
role = await Role.findById(data.roleId)
role = role.toJSON()
role.skillsMatch = 1;
// if skills is provided then use skills to find role
} else if (!_.isUndefined(data.skills)) {
// validate given skillIds and convert them into skill names
Expand All @@ -779,7 +785,7 @@ async function roleSearchRequest (currentUser, data) {

roleSearchRequest.schema = Joi.object()
.keys({
currentUser: Joi.object().required(),
currentUser: Joi.object(),
data: Joi.object().keys({
roleId: Joi.string().uuid(),
jobDescription: Joi.string().max(255),
Expand All @@ -793,28 +799,29 @@ roleSearchRequest.schema = Joi.object()
* @returns {Role} the best matching Role
*/
async function getRoleBySkills (skills) {
const lowerCaseSkills = skills.map(skill => skill.toLowerCase())
// find all roles which includes any of the given skills
const queryCriteria = {
where: { listOfSkills: { [Op.overlap]: skills } },
where: { listOfSkills: { [Op.overlap]: lowerCaseSkills } },
raw: true
}
const roles = await Role.findAll(queryCriteria)
if (roles.length > 0) {
let result = _.each(roles, role => {
// calculate each found roles matching rate
role.matchingRate = _.intersection(role.listOfSkills, skills).length / skills.length
role.skillsMatch = _.intersection(role.listOfSkills, lowerCaseSkills).length / skills.length
// each role can have multiple rates, get the maximum of global rates
role.maxGlobal = _.maxBy(role.rates, 'global').global
})
// sort roles by matchingRate, global rate and name
result = _.orderBy(result, ['matchingRate', 'maxGlobal', 'name'], ['desc', 'desc', 'asc'])
if (result[0].matchingRate >= config.ROLE_MATCHING_RATE) {
// sort roles by skillsMatch, global rate and name
result = _.orderBy(result, ['skillsMatch', 'maxGlobal', 'name'], ['desc', 'desc', 'asc'])
if (result[0].skillsMatch >= config.ROLE_MATCHING_RATE) {
// return the 1st role
return _.omit(result[0], ['matchingRate', 'maxGlobal'])
return _.omit(result[0], ['maxGlobal'])
}
}
// if no matching role found then return Niche role or empty object
return await Role.findOne({ where: { name: { [Op.iLike]: 'Niche' } } }) || {}
// if no matching role found then return Custom role or empty object
return await Role.findOne({ where: { name: { [Op.iLike]: 'Custom' } } }) || {}
}

getRoleBySkills.schema = Joi.object()
Expand Down