Skip to content

Commit c0fe79f

Browse files
committed
Merge branch 'dev' into feature/batch-payments
# Conflicts: # docs/Topcoder-bookings-api.postman_collection.json
2 parents 803f956 + 431157e commit c0fe79f

File tree

4 files changed

+111
-61
lines changed

4 files changed

+111
-61
lines changed

docs/Topcoder-bookings-api.postman_collection.json

Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"info": {
3-
"_postman_id": "83709984-a230-465b-8cdf-88ecb7fb211a",
3+
"_postman_id": "2c9dbe94-39f9-4a01-97e4-70f781fc1364",
44
"name": "Topcoder-bookings-api",
55
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
66
},
@@ -19959,6 +19959,44 @@
1995919959
},
1996019960
"response": []
1996119961
},
19962+
{
19963+
"name": "POST /taas-teams/getSkillsByJobDescription",
19964+
"request": {
19965+
"method": "POST",
19966+
"header": [
19967+
{
19968+
"key": "Authorization",
19969+
"type": "text",
19970+
"value": "Bearer {{token_member}}"
19971+
},
19972+
{
19973+
"key": "Content-Type",
19974+
"type": "text",
19975+
"value": "application/json"
19976+
}
19977+
],
19978+
"body": {
19979+
"mode": "raw",
19980+
"raw": "{\n \"description\": \"nodejs react c++ hello\"\n}",
19981+
"options": {
19982+
"raw": {
19983+
"language": "json"
19984+
}
19985+
}
19986+
},
19987+
"url": {
19988+
"raw": "{{URL}}/taas-teams/getSkillsByJobDescription",
19989+
"host": [
19990+
"{{URL}}"
19991+
],
19992+
"path": [
19993+
"taas-teams",
19994+
"getSkillsByJobDescription"
19995+
]
19996+
}
19997+
},
19998+
"response": []
19999+
},
1996220000
{
1996320001
"name": "GET /taas-teams/:id/members",
1996420002
"request": {
@@ -22690,47 +22728,29 @@
2269022728
}
2269122729
},
2269222730
"response": []
22693-
},
22731+
}
22732+
]
22733+
},
22734+
{
22735+
"name": "Delete Role",
22736+
"item": [
2269422737
{
22695-
"name": "POST /taas-teams/getSkillsByJobDescription",
22696-
"request": {
22697-
"method": "POST",
22698-
"header": [
22699-
{
22700-
"key": "Authorization",
22701-
"type": "text",
22702-
"value": "Bearer {{token_member}}"
22703-
},
22704-
{
22705-
"key": "Content-Type",
22706-
"type": "text",
22707-
"value": "application/json"
22708-
}
22709-
],
22710-
"body": {
22711-
"mode": "raw",
22712-
"raw": "{\n \"description\": \"nodejs react c++ hello\"\n}",
22713-
"options": {
22714-
"raw": {
22715-
"language": "json"
22716-
}
22738+
"name": "delete role with connect user",
22739+
"event": [
22740+
{
22741+
"listen": "test",
22742+
"script": {
22743+
"exec": [
22744+
"pm.test('Status code is 403', function () {\r",
22745+
" pm.response.to.have.status(403);\r",
22746+
" const response = pm.response.json()\r",
22747+
" pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r",
22748+
"});"
22749+
],
22750+
"type": "text/javascript"
2271722751
}
22718-
},
22719-
"url": {
22720-
"raw": "{{URL}}/taas-teams/getSkillsByJobDescription",
22721-
"host": [
22722-
"{{URL}}"
22723-
],
22724-
"path": [
22725-
"taas-teams",
22726-
"getSkillsByJobDescription"
22727-
]
2272822752
}
22729-
},
22730-
"response": []
22731-
},
22732-
{
22733-
"name": "POST /taas-teams/email - member-issue-report",
22753+
],
2273422754
"request": {
2273522755
"method": "DELETE",
2273622756
"header": [
@@ -33107,4 +33127,4 @@
3310733127
]
3310833128
}
3310933129
]
33110-
}
33130+
}

src/common/helper.js

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,15 @@ esIndexPropertyMapping[config.get('esConfig.ES_INDEX_RESOURCE_BOOKING')] = {
221221
updatedBy: { type: 'keyword' }
222222
}
223223
esIndexPropertyMapping[config.get('esConfig.ES_INDEX_ROLE')] = {
224-
name: { type: 'keyword' },
224+
name: {
225+
type: 'keyword',
226+
normalizer: 'lowercaseNormalizer'
227+
},
225228
description: { type: 'keyword' },
226-
listOfSkills: { type: 'keyword' },
229+
listOfSkills: {
230+
type: 'keyword',
231+
normalizer: 'lowercaseNormalizer'
232+
},
227233
rates: {
228234
properties: {
229235
global: { type: 'integer' },
@@ -1199,6 +1205,24 @@ async function getTopcoderSkills (criteria) {
11991205
}
12001206
}
12011207

1208+
/**
1209+
* Function to search and retrive all skills from v5/skills
1210+
* - only returns skills from Topcoder Skills Provider defined by `TOPCODER_SKILL_PROVIDER_ID`
1211+
*
1212+
* @param {Object} criteria the search criteria
1213+
* @returns the request result
1214+
*/
1215+
async function getAllTopcoderSkills (criteria) {
1216+
const skills = await getTopcoderSkills(_.assign(criteria, { page: 1, perPage: 100 }))
1217+
while (skills.page * skills.perPage <= skills.total) {
1218+
const newSkills = await getTopcoderSkills(_.assign(criteria, { page: skills.page + 1, perPage: 100 }))
1219+
skills.result = [...skills.result, ...newSkills.result]
1220+
skills.page = newSkills.page
1221+
skills.total = newSkills.total
1222+
}
1223+
return skills.result
1224+
}
1225+
12021226
/**
12031227
* Function to get skill by id
12041228
* @param {String} skillId the skill Id
@@ -1745,29 +1769,27 @@ async function substituteStringByObject (string, object) {
17451769
return string
17461770
}
17471771

1748-
17491772
/**
17501773
* Get tags from tagging service
17511774
* @param {String} description The challenge description
17521775
* @returns {Array} array of tags
17531776
*/
17541777
async function getTags (description) {
1755-
const data = { text: description, extract_confidence: false}
1756-
const type = "emsi/internal_no_refresh"
1757-
const url = `${config.TC_API}/contest-tagging/${type}`;
1778+
const data = { text: description, extract_confidence: false }
1779+
const type = 'emsi/internal_no_refresh'
1780+
const url = `${config.TC_API}/contest-tagging/${type}`
17581781
const res = await request
17591782
.post(url)
17601783
.set('Accept', 'application/json')
17611784
.send(querystring.stringify(data))
17621785

17631786
localLogger.debug({
17641787
context: 'getTags',
1765-
message: `response body: ${JSON.stringify(res.body)}`,
1766-
});
1767-
return _.get(res, 'body');
1788+
message: `response body: ${JSON.stringify(res.body)}`
1789+
})
1790+
return _.get(res, 'body')
17681791
}
17691792

1770-
17711793
/**
17721794
* @param {Object} currentUser the user performing the action
17731795
* @param {Object} data title of project and any other info
@@ -1819,6 +1841,7 @@ module.exports = {
18191841
getMembers,
18201842
getProjectById,
18211843
getTopcoderSkills,
1844+
getAllTopcoderSkills,
18221845
getSkillById,
18231846
ensureJobById,
18241847
ensureResourceBookingById,

src/services/ResourceBookingService.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -556,8 +556,8 @@ async function searchResourceBookings (currentUser, criteria, options = { return
556556
const { body } = await esClient.search(esQuery)
557557
const resourceBookings = _.map(body.hits.hits, '_source')
558558
// ESClient will return ResourceBookings with it's all nested WorkPeriods
559-
// We re-apply WorkPeriod filters
560-
_.each(workPeriodFilters, (value, key) => {
559+
// We re-apply WorkPeriod filters except userHandle because all WPs share same userHandle
560+
_.each(_.omit(workPeriodFilters, 'workPeriods.userHandle'), (value, key) => {
561561
key = key.split('.')[1]
562562
_.each(resourceBookings, r => {
563563
r.workPeriods = _.filter(r.workPeriods, { [key]: value })
@@ -608,11 +608,16 @@ async function searchResourceBookings (currentUser, criteria, options = { return
608608
queryCriteria.include[0].attributes = { exclude: _.map(queryOpt.excludeWP, f => _.split(f, '.')[1]) }
609609
}
610610
// Apply WorkPeriod filters
611-
_.each(_.pick(criteria, ['workPeriods.startDate', 'workPeriods.endDate', 'workPeriods.userHandle', 'workPeriods.paymentStatus']), (value, key) => {
611+
_.each(_.pick(criteria, ['workPeriods.startDate', 'workPeriods.endDate', 'workPeriods.paymentStatus']), (value, key) => {
612612
key = key.split('.')[1]
613613
queryCriteria.include[0].where[Op.and].push({ [key]: value })
614-
queryCriteria.include[0].required = true
615614
})
615+
if (criteria['workPeriods.userHandle']) {
616+
queryCriteria.include[0].where[Op.and].push({ userHandle: { [Op.iLike]: criteria['workPeriods.userHandle'] } })
617+
}
618+
if (queryCriteria.include[0].where[Op.and].length > 0) {
619+
queryCriteria.include[0].required = true
620+
}
616621
}
617622
// Apply sorting criteria
618623
if (!queryOpt.sortByWP) {

src/services/RoleService.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,18 @@ async function _checkUserPermissionForWriteDeleteRole (currentUser) {
3232
* @returns {undefined}
3333
*/
3434
async function _cleanAndValidateSkillNames (skills) {
35-
// remove duplicates, leading and trailing whitespaces, remove empties and convert to lowercase.
36-
const cleanedSkills = _.uniq(_.filter(_.map(skills, skill => _.toLower(_.trim(skill))), skill => !_.isEmpty(skill)))
35+
// remove duplicates, leading and trailing whitespaces, empties.
36+
const cleanedSkills = _.uniq(_.filter(_.map(skills, skill => _.trim(skill)), skill => !_.isEmpty(skill)))
3737
if (cleanedSkills.length > 0) {
3838
// search skills if they are exists
39-
const { result } = await helper.getTopcoderSkills({ name: _.join(cleanedSkills, ',') })
39+
const result = await helper.getAllTopcoderSkills({ name: _.join(cleanedSkills, ',') })
4040
const skillNames = _.map(result, 'name')
4141
// find skills that not valid
42-
const unValidSkills = _.differenceWith(cleanedSkills, skillNames, (a, b) => _.toLower(a) === _.toLower(b))
42+
const unValidSkills = _.differenceBy(cleanedSkills, skillNames, _.toLower)
4343
if (unValidSkills.length > 0) {
4444
throw new errors.BadRequestError(`skills: "${unValidSkills}" are not valid`)
4545
}
46-
return cleanedSkills
46+
return _.intersectionBy(skillNames, cleanedSkills, _.toLower)
4747
} else {
4848
return null
4949
}
@@ -232,7 +232,7 @@ deleteRole.schema = Joi.object().keys({
232232
*/
233233
async function searchRoles (currentUser, criteria) {
234234
// clean skill names and convert into an array
235-
criteria.skillsList = _.filter(_.map(_.split(_.trim(criteria.skillsList), ','), skill => _.toLower(_.trim(skill))), skill => !_.isEmpty(skill))
235+
criteria.skillsList = _.filter(_.map(_.split(criteria.skillsList, ','), skill => _.trim(skill)), skill => !_.isEmpty(skill))
236236
try {
237237
const esQuery = {
238238
index: config.get('esConfig.ES_INDEX_ROLE'),
@@ -274,7 +274,9 @@ async function searchRoles (currentUser, criteria) {
274274
const filter = { [Op.and]: [] }
275275
// Apply skill name filters. listOfSkills array should include all skills provided in criteria.
276276
if (criteria.skillsList) {
277-
filter[Op.and].push({ listOfSkills: { [Op.contains]: criteria.skillsList } })
277+
_.each(criteria.skillsList, skill => {
278+
filter[Op.and].push(models.Sequelize.literal(`LOWER('${skill}') in (SELECT lower(x) FROM unnest("list_of_skills"::text[]) x)`))
279+
})
278280
}
279281
// Apply name filter, allow partial match and ignore case
280282
if (criteria.keyword) {

0 commit comments

Comments
 (0)