Skip to content

Commit 49b1001

Browse files
authored
Merge pull request #712 from topcoder-platform/dev
TSJR-331 Feature improvements and bug fixes
2 parents 54adb88 + 9f76609 commit 49b1001

File tree

3 files changed

+87
-16
lines changed

3 files changed

+87
-16
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ workflows:
7878
branches:
7979
only:
8080
- dev
81+
- TSJR-246
8182

8283
# Development builds are executed on "develop" branch only.
8384
- "build-qa":

src/common/helper.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,6 +1392,39 @@ async function getSkillById (skillId) {
13921392
return _.pick(res.body, ['id', 'name'])
13931393
}
13941394

1395+
/**
1396+
* Function to get skills by their exact name, via the standarized skills API
1397+
* Used in the job service to validate skill names passed in when creating / updating
1398+
* jobs
1399+
* @param {Array<String>} names - the skill name
1400+
* @returns the request result
1401+
*/
1402+
async function getSkillsByExactNames (names) {
1403+
if (!names || !_.isArray(names) || !names.length) {
1404+
return []
1405+
}
1406+
1407+
const token = await getM2MToken()
1408+
localLogger.debug({
1409+
context: 'getSkillsByNames',
1410+
message: `M2M Token: ${token}`
1411+
})
1412+
1413+
const url = `${config.TC_API}/standardized-skills/skills?${names.map(skill => `name=${encodeURIComponent(skill)}`).join('&')}`
1414+
const res = await request
1415+
.get(url)
1416+
.set('Authorization', `Bearer ${token}`)
1417+
.set('Content-Type', 'application/json')
1418+
.set('Accept', 'application/json')
1419+
1420+
localLogger.debug({
1421+
context: 'getSkillsByNames',
1422+
message: `response body of GET ${url} is ${JSON.stringify(res.body)}`
1423+
})
1424+
1425+
return res.body
1426+
}
1427+
13951428
/**
13961429
* Encapsulate the getUserByExternalId function.
13971430
* Make sure a user exists in ubahn(/v5/users) and return the id of the user.
@@ -2139,16 +2172,14 @@ async function getMembersSuggest (fragment) {
21392172
async function registerSkills (job) {
21402173
const token = await getM2MToken()
21412174
const body = {
2142-
workId: job.id,
2143-
workTypeId: config.STANDARDIZED_SKILL_WORK_TYPE_ID,
21442175
skillIds: job.skills
21452176
}
21462177
localLogger.debug({
21472178
context: 'registerSkills',
21482179
message: `request: ${JSON.stringify(body)}`
21492180
})
21502181
const res = await request
2151-
.post(`${config.TC_API}/standardized-skills/work-skills`)
2182+
.post(`${config.TC_API}/standardized-skills/job-skills/${job.id}`)
21522183
.set('Authorization', `Bearer ${token}`)
21532184
.set('Content-Type', 'application/json')
21542185
.set('Accept', 'application/json')
@@ -2363,5 +2394,6 @@ module.exports = {
23632394
runExclusiveInterviewEventHandler,
23642395
runExclusiveByNamedMutex,
23652396
signZoomLink,
2366-
verifyZoomLinkToken
2397+
verifyZoomLinkToken,
2398+
getSkillsByExactNames
23672399
}

src/services/JobService.js

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const esClient = helper.getESClient()
2828
* @param {String} jobId the job id
2929
* @returns {Array} the list of candidates
3030
*/
31-
async function _getJobCandidates (jobId) {
31+
async function _getJobCandidates(jobId) {
3232
const { body } = await esClient.search({
3333
index: config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
3434
body: {
@@ -60,7 +60,7 @@ async function _getJobCandidates (jobId) {
6060
* @param {Array} skills the list of skills
6161
* @returns {undefined}
6262
*/
63-
async function _validateSkills (skills) {
63+
async function _validateSkills(skills) {
6464
const responses = await Promise.all(
6565
skills.map(
6666
skill => helper.getSkillById(skill.skillId)
@@ -81,13 +81,31 @@ async function _validateSkills (skills) {
8181
}
8282
}
8383

84+
/**
85+
* Validate by exact match on name field if all skills exist.
86+
*
87+
* @param {Array} skillNames the list of skill names to validate
88+
* @returns {Array} the list of matching skill objects
89+
*/
90+
async function _validateAndGetSkillsByNames(skillNames) {
91+
const skills = await helper.getSkillsByExactNames(skillNames)
92+
93+
if (skills.length !== skillNames.length) {
94+
const foundSkillNames = skills.map(skill => skill.name)
95+
const notFoundSkillNames = _.difference(skillNames, foundSkillNames)
96+
throw new errors.BadRequestError(`Invalid skills: [${notFoundSkillNames}]`)
97+
}
98+
99+
return skills
100+
}
101+
84102
/**
85103
* Validate if all roles exist.
86104
*
87105
* @param {Array} roles the list of roles
88106
* @returns {undefined}
89107
*/
90-
async function _validateRoles (roles) {
108+
async function _validateRoles(roles) {
91109
const foundRolesObj = await models.Role.findAll({
92110
where: {
93111
id: roles
@@ -109,7 +127,7 @@ async function _validateRoles (roles) {
109127
* @param {String} projectId the project id
110128
* @returns {undefined}
111129
*/
112-
async function _checkUserPermissionForGetJob (currentUser, projectId) {
130+
async function _checkUserPermissionForGetJob(currentUser, projectId) {
113131
if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager) {
114132
await helper.checkIsMemberOfProject(currentUser.userId, projectId)
115133
}
@@ -122,7 +140,7 @@ async function _checkUserPermissionForGetJob (currentUser, projectId) {
122140
* @param {Boolean} fromDb flag if query db for data or not
123141
* @returns {Object} the job
124142
*/
125-
async function getJob (currentUser, id, fromDb = false) {
143+
async function getJob(currentUser, id, fromDb = false) {
126144
if (!fromDb) {
127145
try {
128146
const job = await esClient.get({
@@ -170,7 +188,7 @@ getJob.schema = Joi.object().keys({
170188
* @params {Object} job the job to be created
171189
* @returns {Object} the created job
172190
*/
173-
async function createJob (currentUser, job, onTeamCreating) {
191+
async function createJob(currentUser, job, onTeamCreating) {
174192
// check user permission
175193
if (!currentUser.hasManagePermission && !currentUser.isMachine) {
176194
await helper.checkIsMemberOfProject(currentUser.userId, job.projectId)
@@ -258,10 +276,26 @@ createJob.schema = Joi.object()
258276
* @params {Object} data the data to be updated
259277
* @returns {Object} the updated job
260278
*/
261-
async function updateJob (currentUser, id, data) {
279+
async function updateJob(currentUser, id, data) {
280+
logger.debug({ component: 'JobService', context: 'updateJob start', message: `Arguments: ${JSON.stringify(currentUser)} job id: ${id} data: ${JSON.stringify(data)}` })
281+
282+
let skills = data.skills
283+
262284
if (data.skills) {
263285
await _validateSkills(data.skills)
286+
287+
// Compact the skills to *just* the IDs for saving to ES
288+
data.skills = _.chain(skills).map('skillId').uniq().compact().value()
289+
}
290+
if (data.skillNames) {
291+
skills = await _validateAndGetSkillsByNames(data.skillNames)
292+
293+
data = _.omit(data, 'skillNames')
294+
295+
// Compact the skills to *just* the IDs for saving to ES
296+
data.skills = _.chain(skills).map('id').uniq().compact().value()
264297
}
298+
265299
if (data.roleIds) {
266300
data.roleIds = _.uniq(data.roleIds)
267301
await _validateRoles(data.roleIds)
@@ -288,10 +322,13 @@ async function updateJob (currentUser, id, data) {
288322

289323
let entity
290324
try {
325+
logger.debug({ component: 'JobService', context: 'updateJob update transaction', message: `Data: ${JSON.stringify(data)}` })
326+
291327
await sequelize.transaction(async (t) => {
292328
const updated = await job.update(data, { transaction: t })
293329
entity = updated.toJSON()
294330
await processUpdate(entity)
331+
await helper.registerSkills(job)
295332
})
296333
} catch (e) {
297334
if (entity) {
@@ -312,7 +349,7 @@ async function updateJob (currentUser, id, data) {
312349
* @params {Object} data the data to be updated
313350
* @returns {Object} the updated job
314351
*/
315-
async function partiallyUpdateJob (currentUser, id, data) {
352+
async function partiallyUpdateJob(currentUser, id, data) {
316353
return updateJob(currentUser, id, data, false)
317354
}
318355

@@ -332,7 +369,8 @@ partiallyUpdateJob.schema = Joi.object()
332369
resourceType: Joi.stringAllowEmpty().allow(null),
333370
rateType: Joi.rateType().allow(null),
334371
workload: Joi.workload().allow(null),
335-
skills: Joi.array().items(Joi.string().uuid()),
372+
skills: Joi.array().allow(null),
373+
skillNames: Joi.array().allow(null),
336374
isApplicationPageActive: Joi.boolean(),
337375
minSalary: Joi.number().integer(),
338376
maxSalary: Joi.number().integer(),
@@ -359,7 +397,7 @@ partiallyUpdateJob.schema = Joi.object()
359397
* @params {Object} data the data to be updated
360398
* @returns {Object} the updated job
361399
*/
362-
async function fullyUpdateJob (currentUser, id, data) {
400+
async function fullyUpdateJob(currentUser, id, data) {
363401
return updateJob(currentUser, id, data, true)
364402
}
365403

@@ -401,7 +439,7 @@ fullyUpdateJob.schema = Joi.object().keys({
401439
* @params {Object} currentUser the user who perform this operation
402440
* @params {String} id the job id
403441
*/
404-
async function deleteJob (currentUser, id) {
442+
async function deleteJob(currentUser, id) {
405443
// check user permission
406444
if (!currentUser.hasManagePermission && !currentUser.isMachine) {
407445
throw new errors.ForbiddenError('You are not allowed to perform this action!')
@@ -432,7 +470,7 @@ deleteJob.schema = Joi.object().keys({
432470
* @params {Object} options the extra options to control the function
433471
* @returns {Object} the search result, contain total/page/perPage and result array
434472
*/
435-
async function searchJobs (currentUser, criteria, options = { returnAll: false }) {
473+
async function searchJobs(currentUser, criteria, options = { returnAll: false }) {
436474
// check user permission
437475
if (!currentUser.hasManagePermission && !currentUser.isMachine && !currentUser.isConnectManager && !options.returnAll) {
438476
if (!criteria.projectId) { // regular user can only search with filtering by "projectId"

0 commit comments

Comments
 (0)