Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 8d21bad

Browse files
committed
API to searchs kills associated with an organization
1 parent 845f92d commit 8d21bad

File tree

4 files changed

+157
-19
lines changed

4 files changed

+157
-19
lines changed

docs/UBahn_API.postman_collection.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,58 @@
113113
},
114114
"response": []
115115
},
116+
{
117+
"name": "{{HOST}}/search/skills",
118+
"event": [
119+
{
120+
"listen": "test",
121+
"script": {
122+
"id": "d56349bd-a797-47cf-b6bf-2ecfbd342815",
123+
"exec": [
124+
""
125+
],
126+
"type": "text/javascript"
127+
}
128+
}
129+
],
130+
"request": {
131+
"method": "GET",
132+
"header": [
133+
{
134+
"key": "Authorization",
135+
"type": "text",
136+
"value": "Bearer {{token}}"
137+
},
138+
{
139+
"key": "Content-Type",
140+
"name": "Content-Type",
141+
"type": "text",
142+
"value": "application/json"
143+
}
144+
],
145+
"url": {
146+
"raw": "{{HOST}}/search/userAchievements?organizationId=36ed815b-3da1-49f1-a043-aaed0a4e81ad&keyword=Topcoder",
147+
"host": [
148+
"{{HOST}}"
149+
],
150+
"path": [
151+
"search",
152+
"userAchievements"
153+
],
154+
"query": [
155+
{
156+
"key": "organizationId",
157+
"value": "36ed815b-3da1-49f1-a043-aaed0a4e81ad"
158+
},
159+
{
160+
"key": "keyword",
161+
"value": "Topcoder"
162+
}
163+
]
164+
}
165+
},
166+
"response": []
167+
},
116168
{
117169
"name": "{{HOST}}/search/users",
118170
"event": [

src/common/es-helper.js

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -676,33 +676,53 @@ function hasNonAlphaNumeric (text) {
676676
* @param keyword the search keyword
677677
* @returns array of skillIds
678678
*/
679-
async function searchSkills (keyword) {
679+
async function searchSkills (keyword, skillProviderIds) {
680680
const queryDoc = DOCUMENTS.skill
681681
keyword = escapeRegex(keyword)
682682
const query = hasNonAlphaNumeric(keyword) ? `\\*${keyword}\\*` : `*${keyword}*`
683683

684+
const keywordSearchClause = {
685+
query_string: {
686+
default_field: 'name',
687+
minimum_should_match: '100%',
688+
query
689+
}
690+
}
691+
692+
const searchClause = {
693+
query: {}
694+
}
695+
696+
if (skillProviderIds == null) {
697+
searchClause.query = keywordSearchClause
698+
searchClause._source = 'id'
699+
} else {
700+
searchClause.query = {
701+
bool: {
702+
filter: [{
703+
terms: {
704+
[`${RESOURCE_FILTER.skill.skillProviderId.queryField}.keyword`]: skillProviderIds
705+
}
706+
}],
707+
must: [keywordSearchClause]
708+
}
709+
}
710+
}
711+
684712
const esQuery = {
685713
index: queryDoc.index,
686714
type: queryDoc.type,
687-
body: {
688-
query: {
689-
query_string: {
690-
default_field: 'name',
691-
minimum_should_match: '100%',
692-
query
693-
}
694-
},
695-
_source: 'id'
696-
}
715+
body: searchClause
697716
}
698717

699718
logger.debug(`ES query for searching skills: ${JSON.stringify(esQuery, null, 2)}`)
700719
const results = await esClient.search(esQuery)
701-
return results.hits.hits.map(hit => hit._source.id)
720+
721+
return results.hits.hits.map(hit => hit._source)
702722
}
703723

704724
async function setUserSearchClausesToEsQuery (boolClause, keyword) {
705-
const skillIds = await searchSkills(keyword)
725+
const skillIds = (await searchSkills(keyword)).map(skill => skill.id)
706726
boolClause.should.push({
707727
query_string: {
708728
fields: ['firstName', 'lastName', 'handle'],
@@ -866,6 +886,27 @@ function buildEsQueryToGetAttributeValues (attributeId, attributeValue, size) {
866886
return esQuery
867887
}
868888

889+
function buildEsQueryToGetSkillProviderIds (organizationId) {
890+
const queryDoc = DOCUMENTS.organization
891+
892+
const esQuery = {
893+
index: queryDoc.index,
894+
type: queryDoc.type,
895+
body: {
896+
size: 1000,
897+
query: {
898+
term: {
899+
'id.keyword': {
900+
value: organizationId
901+
}
902+
}
903+
}
904+
}
905+
}
906+
907+
return esQuery
908+
}
909+
869910
async function resolveUserFilterFromDb (filter, { handle }, organizationId) {
870911
const DBHelper = require('../models/index').DBHelper
871912

@@ -1391,6 +1432,33 @@ async function searchUsers (authUser, filter, params) {
13911432
}
13921433
}
13931434

1435+
/**
1436+
* Search for skills matching the given keyword and are part of the given organization
1437+
* @param {Object} param0 the organizationId and keyword
1438+
*/
1439+
1440+
async function searchSkillsInOrganization ({ organizationId, keyword }) {
1441+
const esQueryToGetSkillProviders = buildEsQueryToGetSkillProviderIds(organizationId)
1442+
logger.debug(`ES query to get skill provider ids: ${JSON.stringify(esQueryToGetSkillProviders, null, 2)}`)
1443+
1444+
const esResultOfQueryToGetSkillProviders = await esClient.search(esQueryToGetSkillProviders)
1445+
logger.debug(`ES result: ${JSON.stringify(esResultOfQueryToGetSkillProviders, null, 2)}`)
1446+
1447+
const skillProviderIds = _.flatten(esResultOfQueryToGetSkillProviders.hits.hits.map(hit => hit._source.skillProviders == null ? [] : hit._source.skillProviders.map(sp => sp.id)))
1448+
logger.debug(`Organization ${organizationId} yielded skillProviderIds: ${JSON.stringify(skillProviderIds, null, 2)}`)
1449+
1450+
const skills = await searchSkills(keyword, skillProviderIds)
1451+
1452+
return {
1453+
result: skills.map(skill => ({
1454+
name: skill.name,
1455+
skillId: skill.id,
1456+
skillProviderId: skill.skillProviderId
1457+
// skillProviderName: 'TODO'
1458+
}))
1459+
}
1460+
}
1461+
13941462
/**
13951463
* Searches for matching values for the given attribute value, under the given attribute id
13961464
* @param {Object} param0 The attribute id and the attribute value properties
@@ -1427,19 +1495,19 @@ async function searchAchievementValues ({ organizationId, keyword }) {
14271495
const esResult = await esClient.search(esQuery)
14281496
logger.debug(`ES response ${JSON.stringify(esResult, null, 2)}`)
14291497
const result = esResult.aggregations.achievements.buckets.map(a => {
1430-
let achievementName = a.key
1498+
const achievementName = a.key
14311499
let achievementId = null
1432-
1433-
for (let achievement of a.ids.hits.hits[0]._source.achievements) {
1434-
if (achievement.name == achievementName) {
1500+
1501+
for (const achievement of a.ids.hits.hits[0]._source.achievements) {
1502+
if (achievement.name === achievementName) {
14351503
achievementId = achievement.id
1436-
break;
1504+
break
14371505
}
14381506
}
14391507
return {
14401508
id: achievementId,
14411509
name: achievementName
1442-
};
1510+
}
14431511
})
14441512

14451513
return {
@@ -1451,6 +1519,7 @@ module.exports = {
14511519
searchElasticSearch,
14521520
getFromElasticSearch,
14531521
searchUsers,
1522+
searchSkillsInOrganization,
14541523
searchAttributeValues,
14551524
searchAchievementValues
14561525
}

src/modules/search/controller.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ async function searchUsers (req, res) {
1313
res.send(result.result)
1414
}
1515

16+
/**
17+
* Search for skills in organization
18+
*/
19+
async function searchSkills (req, res) {
20+
const result = await esHelper.searchSkillsInOrganization(req.query)
21+
res.send(result.result)
22+
}
23+
1624
/**
1725
* Search for attribute values
1826
*/
@@ -31,6 +39,7 @@ async function searchAchievementValues (req, res) {
3139

3240
module.exports = {
3341
searchUsers,
42+
searchSkills,
3443
searchAttributeValues,
3544
searchAchievementValues
3645
}

src/modules/search/route.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ module.exports = {
1414
scopes: ['read:user', 'all:user']
1515
}
1616
},
17+
'/search/skills': {
18+
get: {
19+
method: Controller.searchSkills
20+
auth: 'jwt',
21+
access: consts.AdminUser,
22+
scopes: ['create:userAttribute', 'all:userAttribute']
23+
}
24+
},
1725
'/search/userAttributes': {
1826
get: {
1927
method: Controller.searchAttributeValues,

0 commit comments

Comments
 (0)