diff --git a/config/default.js b/config/default.js index 726c40fa..3298fb10 100644 --- a/config/default.js +++ b/config/default.js @@ -41,6 +41,8 @@ module.exports = { TOPCODER_USERS_API: process.env.TOPCODER_USERS_API || 'https://api.topcoder-dev.com/v3/users', // the api to find topcoder members TOPCODER_MEMBERS_API: process.env.TOPCODER_MEMBERS_API || 'https://api.topcoder-dev.com/v5/members', + // the v3 api to find topcoder members + TOPCODER_MEMBERS_API_V3: process.env.TOPCODER_MEMBERS_API_V3 || 'https://api.topcoder-dev.com/v3/members', // rate limit of requests to user api MAX_PARALLEL_REQUEST_TOPCODER_USERS_API: process.env.MAX_PARALLEL_REQUEST_TOPCODER_USERS_API || 100, diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index a3e40abe..35244bee 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "15f10b58-dda5-4aaf-96e5-061a5c901717", + "_postman_id": "87477d86-2d08-40b6-93c6-99a394193e28", "name": "Topcoder-bookings-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -19312,6 +19312,247 @@ } ] }, + { + "name": "Member Suggestion", + "item": [ + { + "name": "get member suggestion successfully with administrator", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_administrator}}" + } + ], + "url": { + "raw": "{{URL}}/taas-teams/members-suggest/maxceem", + "host": [ + "{{URL}}" + ], + "path": [ + "taas-teams", + "members-suggest", + "maxceem" + ] + } + }, + "response": [] + }, + { + "name": "get member suggestion successfully with booking manager", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_bookingManager}}" + } + ], + "url": { + "raw": "{{URL}}/taas-teams/members-suggest/isbilir", + "host": [ + "{{URL}}" + ], + "path": [ + "taas-teams", + "members-suggest", + "isbilir" + ] + } + }, + "response": [] + }, + { + "name": "get member suggestion with connect manager", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 403', function () {\r", + " pm.response.to.have.status(403);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_connect_manager_pshahcopmanag2}}" + } + ], + "url": { + "raw": "{{URL}}/taas-teams/members-suggest/isbilir", + "host": [ + "{{URL}}" + ], + "path": [ + "taas-teams", + "members-suggest", + "isbilir" + ] + } + }, + "response": [] + }, + { + "name": "get member suggestion with member", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 403', function () {\r", + " pm.response.to.have.status(403);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_member}}" + } + ], + "url": { + "raw": "{{URL}}/taas-teams/members-suggest/isbilir", + "host": [ + "{{URL}}" + ], + "path": [ + "taas-teams", + "members-suggest", + "isbilir" + ] + } + }, + "response": [] + }, + { + "name": "get member suggestion with m2m", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 403', function () {\r", + " pm.response.to.have.status(403);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"You are not allowed to perform this action!\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token_m2m_all_job}}" + } + ], + "url": { + "raw": "{{URL}}/taas-teams/members-suggest/isbilir", + "host": [ + "{{URL}}" + ], + "path": [ + "taas-teams", + "members-suggest", + "isbilir" + ] + } + }, + "response": [] + }, + { + "name": "get member suggestion with invalid token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 401', function () {\r", + " pm.response.to.have.status(401);\r", + " const response = pm.response.json()\r", + " pm.expect(response.message).to.eq(\"Invalid Token.\")\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer invalid" + } + ], + "url": { + "raw": "{{URL}}/taas-teams/members-suggest/isbilir", + "host": [ + "{{URL}}" + ], + "path": [ + "taas-teams", + "members-suggest", + "isbilir" + ] + } + }, + "response": [] + } + ] + }, { "name": "GET /taas-teams", "request": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 26b389f4..b7705889 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -3296,6 +3296,41 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + /taas-teams/members-suggest/{fragment}: + get: + tags: + - Teams + description: | + Returns suggested members for the given handle fragment + security: + - bearerAuth: [] + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/SuggestedMember" + "401": + description: Not authenticated + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /taas-roles: post: tags: @@ -5404,6 +5439,32 @@ components: type: string format: uuid description: "The user Id who updated the role last time.(Will get the user info from the token)" + SuggestedMember: + properties: + userId: + type: number + example: 40157055 + description: the user id + handle: + type: string + example: maxceemdev + description: the user handle + photoURL: + type: string + example: https://topcoder-dev-media.s3.amazonaws.com/member/profile/maxceem13-1587184611143.jpeg + description: the photo url + firstName: + type: string + example: Max + description: the firstname of the user + lastName: + type: string + example: Max + description: the lastname of the user + maxRating: + type: number + example: 1200 + description: the maximum rating of the user RoleRequestBody: required: - name diff --git a/src/common/helper.js b/src/common/helper.js index 3e452b6f..851f6907 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -1988,6 +1988,26 @@ function removeTextFormatting (text) { return text } +/** + * Function to get member suggestions + * @param {string} fragment the handle fragment + * @return the request result + */ +async function getMembersSuggest (fragment) { + const token = await getM2MToken() + const url = `${config.TOPCODER_MEMBERS_API_V3}/_suggest/${encodeURIComponent(fragment)}` + const res = await request + .get(url) + .set('Authorization', `Bearer ${token}`) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json') + localLogger.debug({ + context: 'getMembersSuggest', + message: `response body: ${JSON.stringify(res.body)}` + }) + return res.body +} + module.exports = { getParamFromCliArgs, promptUser, @@ -2048,5 +2068,6 @@ module.exports = { substituteStringByObject, createProject, getMemberGroups, - removeTextFormatting + removeTextFormatting, + getMembersSuggest } diff --git a/src/controllers/TeamController.js b/src/controllers/TeamController.js index 998f9a81..65e5262f 100644 --- a/src/controllers/TeamController.js +++ b/src/controllers/TeamController.js @@ -146,6 +146,15 @@ async function searchSkills (req, res) { res.send(result.result) } +/** + * Suggest members + * @param req the request + * @param res the response + */ +async function suggestMembers (req, res) { + res.send(await service.suggestMembers(req.authUser, req.params.fragment)) +} + module.exports = { searchTeams, getTeam, @@ -159,5 +168,6 @@ module.exports = { getSkillsByJobDescription, roleSearchRequest, createTeam, - searchSkills + searchSkills, + suggestMembers } diff --git a/src/routes/TeamRoutes.js b/src/routes/TeamRoutes.js index b2415e17..a4c1ca5e 100644 --- a/src/routes/TeamRoutes.js +++ b/src/routes/TeamRoutes.js @@ -99,5 +99,13 @@ module.exports = { auth: 'jwt', scopes: [constants.Scopes.CREATE_TAAS_TEAM] } + }, + '/taas-teams/members-suggest/:fragment': { + get: { + controller: 'TeamController', + method: 'suggestMembers', + auth: 'jwt', + scopes: [] + } } } diff --git a/src/services/TeamService.js b/src/services/TeamService.js index 978c91b9..aad37814 100644 --- a/src/services/TeamService.js +++ b/src/services/TeamService.js @@ -771,7 +771,7 @@ async function roleSearchRequest (currentUser, data) { // if only job description is provided, collect skill names from description const tags = await getSkillsByJobDescription({ description: data.jobDescription }) const skills = _.map(tags, 'tag') - + // add skills to roleSearchRequest and get best matching role const [skillIds, roleList] = await Promise.all([getSkillIdsByNames(skills), getRoleBySkills(skills)]) data.skills = skillIds @@ -1137,6 +1137,25 @@ searchSkills.schema = Joi.object().keys({ }).required() }).required() +/** + * Get member suggestions + * @param {object} currentUser the user performing the operation. + * @param {string} fragment the user's handle fragment + * @returns {Array} the search result, contains result array + */ +async function suggestMembers (currentUser, fragment) { + if (!currentUser.hasManagePermission) { + throw new errors.ForbiddenError('You are not allowed to perform this action!') + } + const { result } = await helper.getMembersSuggest(fragment) + return result.content.slice(0, 100) +} + +suggestMembers.schema = Joi.object().keys({ + currentUser: Joi.object().required(), + fragment: Joi.string().required() +}).required() + module.exports = { searchTeams, getTeam, @@ -1155,5 +1174,6 @@ module.exports = { createRoleSearchRequest, isExternalMember, createTeam, - searchSkills + searchSkills, + suggestMembers }