Skip to content

Commit 8a81307

Browse files
authored
Merge pull request #8 from topcoder-platform/develop
Rating color and fix's to Member Profile & Stats
2 parents a8380b6 + 2f9f9b4 commit 8a81307

15 files changed

+257
-142
lines changed

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,14 @@ The following parameters can be set in config files or in env variables:
3535
- ES: config object for Elasticsearch
3636
- ES.HOST: Elasticsearch host
3737
- ES.API_VERSION: Elasticsearch API version
38-
- ES.ES_INDEX: Elasticsearch index name for member
39-
- ES.ES_TYPE: Elasticsearch index type for member
38+
- ES.MEMBER_PROFILE_ES_INDEX: Elasticsearch index name for member profile
39+
- ES.MEMBER_PROFILE_ES_TYPE: Elasticsearch index type for member profile
4040
- ES.MEMBER_TRAIT_ES_INDEX: Elasticsearch index name for member trait
4141
- ES.MEMBER_TRAIT_ES_TYPE: Elasticsearch index type for member trait
42+
- ES.MEMBER_STATS_ES_INDEX: Elasticsearch index name for member stats
43+
- ES.MEMBER_STATS_ES_TYPE: Elasticsearch index type for member stats
44+
- ES.MEMBER_SKILLS_ES_INDEX: Elasticsearch index name for member skills
45+
- ES.MEMBER_SKILLS_ES_TYPE: Elasticsearch index type for member skills
4246
- FILE_UPLOAD_SIZE_LIMIT: the file upload size limit in bytes
4347
- PHOTO_URL_TEMPLATE: photo URL template, its <key> will be replaced with S3 object key
4448
- VERIFY_TOKEN_EXPIRATION: verify token expiration in minutes

app-bootstrap.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ global.Promise = require('bluebird')
55
const Joi = require('joi')
66

77
Joi.page = () => Joi.number().integer().min(1).default(1)
8-
Joi.perPage = () => Joi.number().integer().min(1).max(100).default(20)
8+
Joi.perPage = () => Joi.number().integer().min(1).max(100).default(10)
9+
Joi.sort = () => Joi.string().default("asc")

config/default.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ module.exports = {
4545
HOST: process.env.ES_HOST || 'localhost:9200',
4646
API_VERSION: process.env.ES_API_VERSION || '6.8',
4747
// member index
48-
ES_INDEX: process.env.ES_INDEX || 'members-2020-01',
48+
MEMBER_PROFILE_ES_INDEX: process.env.MEMBER_PROFILE_ES_INDEX || 'members-2020-01',
4949
// member type, ES 6.x accepts only 1 Type per index and it's mandatory to define it
50-
ES_TYPE: process.env.ES_TYPE || 'profiles',
50+
MEMBER_PROFILE_ES_TYPE: process.env.MEMBER_PROFILE_ES_TYPE || 'profiles',
5151
MEMBER_TRAIT_ES_INDEX: process.env.MEMBER_TRAIT_ES_INDEX || 'members-2020-01',
5252
MEMBER_TRAIT_ES_TYPE: process.env.MEMBER_TRAIT_ES_TYPE || 'profiletraits',
5353
MEMBER_STATS_ES_INDEX: process.env.MEMBER_STATS_ES_INDEX || 'memberstats-2020-01',
54-
MEMBER_STATS_ES_TYPE: process.env.MEMBER_STATS_ES_TYPE || 'stats'
54+
MEMBER_STATS_ES_TYPE: process.env.MEMBER_STATS_ES_TYPE || 'stats',
55+
MEMBER_SKILLS_ES_INDEX: process.env.MEMBER_SKILLS_ES_INDEX || 'memberskills-2020-01',
56+
MEMBER_SKILLS_ES_TYPE: process.env.MEMBER_SKILLS_ES_TYPE || 'skills'
5557
},
5658

5759
// health check timeout in milliseconds

src/common/helper.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ const uuid = require('uuid/v4')
1313
const querystring = require('querystring')
1414
const request = require('request');
1515

16+
// Color schema for Ratings
17+
const RATING_COLORS = [{
18+
color: '#9D9FA0' /* Grey */,
19+
limit: 900,
20+
}, {
21+
color: '#69C329' /* Green */,
22+
limit: 1200,
23+
}, {
24+
color: '#616BD5' /* Blue */,
25+
limit: 1500,
26+
}, {
27+
color: '#FCD617' /* Yellow */,
28+
limit: 2200,
29+
}, {
30+
color: '#EF3A3A' /* Red */,
31+
limit: Infinity,
32+
}];
33+
1634
// Bus API Client
1735
let busApiClient
1836

@@ -478,6 +496,8 @@ function cleanUpStatistics (stats, fields) {
478496
if (typeof stats[count].maxRating == "string") {
479497
stats[count].maxRating = JSON.parse(stats[count].maxRating)
480498
}
499+
// set the rating color
500+
stats[count].maxRating.ratingColor = this.getRatingColor(stats[count].maxRating.rating)
481501
}
482502
if (stats[count].hasOwnProperty("DATA_SCIENCE")) {
483503
if (typeof stats[count].DATA_SCIENCE == "string") {
@@ -602,6 +622,12 @@ function findTagById (data, id) {
602622
return _.find(data, { 'id': id });
603623
}
604624

625+
function getRatingColor(rating) {
626+
let i = 0; const r = Number(rating);
627+
while (RATING_COLORS[i].limit <= r) i += 1;
628+
return RATING_COLORS[i].color || 'black';
629+
}
630+
605631
module.exports = {
606632
wrapExpress,
607633
autoWrapExpress,
@@ -625,5 +651,6 @@ module.exports = {
625651
cleanupSkills,
626652
mergeSkills,
627653
getAllTags,
628-
findTagById
654+
findTagById,
655+
getRatingColor
629656
}

src/common/logger.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,16 @@ logger.decorateWithLogging = (service) => {
8585
const params = method.params || getParams(method)
8686
service[name] = async function () {
8787
logger.debug(`ENTER ${name}`)
88-
logger.debug('input arguments')
89-
const args = Array.prototype.slice.call(arguments)
90-
logger.debug(util.inspect(_sanitizeObject(_combineObject(params, args))))
88+
// logger.debug('input arguments')
89+
// const args = Array.prototype.slice.call(arguments)
90+
// logger.debug(util.inspect(_sanitizeObject(_combineObject(params, args))))
9191
try {
9292
const result = await method.apply(this, arguments)
9393
logger.debug(`EXIT ${name}`)
94-
logger.debug('output arguments')
95-
if (result !== null && result !== undefined) {
96-
logger.debug(util.inspect(_sanitizeObject(result)))
97-
}
94+
// logger.debug('output arguments')
95+
// if (result !== null && result !== undefined) {
96+
// logger.debug(util.inspect(_sanitizeObject(result)))
97+
// }
9898
return result
9999
} catch (e) {
100100
logger.logFullError(e, name)

src/init-es.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ const client = helper.getESClient()
1414

1515
const initES = async () => {
1616
if (process.argv.length === 3 && process.argv[2] === 'force') {
17-
logger.info(`Delete index ${config.ES.ES_INDEX} if any.`)
17+
logger.info(`Delete index ${config.ES.MEMBER_PROFILE_ES_INDEX} if any.`)
1818
try {
19-
await client.indices.delete({ index: config.ES.ES_INDEX })
19+
await client.indices.delete({ index: config.ES.MEMBER_PROFILE_ES_INDEX })
2020
} catch (err) {
2121
// ignore
2222
}
@@ -28,14 +28,14 @@ const initES = async () => {
2828
}
2929
}
3030

31-
let exists = await client.indices.exists({ index: config.ES.ES_INDEX })
31+
let exists = await client.indices.exists({ index: config.ES.MEMBER_PROFILE_ES_INDEX })
3232
if (exists) {
33-
logger.info(`The index ${config.ES.ES_INDEX} exists.`)
33+
logger.info(`The index ${config.ES.MEMBER_PROFILE_ES_INDEX} exists.`)
3434
} else {
35-
logger.info(`The index ${config.ES.ES_INDEX} will be created.`)
35+
logger.info(`The index ${config.ES.MEMBER_PROFILE_ES_INDEX} will be created.`)
3636

3737
const body = { mappings: {} }
38-
body.mappings[config.get('ES.ES_TYPE')] = {
38+
body.mappings[config.get('ES.MEMBER_PROFILE_ES_TYPE')] = {
3939
properties: {
4040
handleLower: { type: 'keyword' },
4141
handle: { type: 'keyword' },
@@ -45,7 +45,7 @@ const initES = async () => {
4545
}
4646

4747
await client.indices.create({
48-
index: config.ES.ES_INDEX,
48+
index: config.ES.MEMBER_PROFILE_ES_INDEX,
4949
body
5050
})
5151
}

src/routes.js

+9-8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ module.exports = {
1414
method: 'checkHealth'
1515
}
1616
},
17+
'/members/search/members': {
18+
get: {
19+
controller: 'SearchController',
20+
method: 'searchMembers',
21+
auth: 'jwt',
22+
allowNoToken: true,
23+
scopes: [MEMBERS.READ, MEMBERS.ALL]
24+
}
25+
},
1726
'/members/:handle': {
1827
get: {
1928
controller: 'MemberController',
@@ -117,14 +126,6 @@ module.exports = {
117126
controller: 'MiscController',
118127
method: 'getMemberFinancial',
119128
auth: 'jwt',
120-
scopes: [MEMBERS.READ, MEMBERS.ALL]
121-
}
122-
},
123-
'/members': {
124-
get: {
125-
controller: 'SearchController',
126-
method: 'searchMembers',
127-
auth: 'jwt',
128129
allowNoToken: true,
129130
scopes: [MEMBERS.READ, MEMBERS.ALL]
130131
}

src/scripts/seed-data.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,8 @@ async function seedData () {
390390
await helper.create('Member', member)
391391
// create member in ES
392392
await esClient.create({
393-
index: config.ES.ES_INDEX,
394-
type: config.ES.ES_TYPE,
393+
index: config.ES.MEMBER_PROFILE_ES_INDEX,
394+
type: config.ES.MEMBER_PROFILE_ES_TYPE,
395395
id: member.handleLower,
396396
body: member,
397397
refresh: 'true' // refresh ES so that it is visible for read operations instantly

src/scripts/view-es-data.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const esClient = helper.getESClient()
1818
async function showESData () {
1919
const result = await esClient.search({
2020
index: indexName,
21-
type: config.get('ES.ES_TYPE') // type name is same for all indices
21+
type: config.get('ES.MEMBER_PROFILE_ES_TYPE') // type name is same for all indices
2222
})
2323
return result.hits.hits || []
2424
}

src/services/MemberService.js

+21-5
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ const uuid = require('uuid/v4')
88
const config = require('config')
99
const helper = require('../common/helper')
1010
const logger = require('../common/logger')
11+
const statisticsService = require('./StatisticsService')
1112
const errors = require('../common/errors')
1213
const constants = require('../../app-constants')
1314
const HttpStatus = require('http-status-codes')
1415

1516
const esClient = helper.getESClient()
1617

1718
const MEMBER_FIELDS = ['userId', 'handle', 'handleLower', 'firstName', 'lastName', 'tracks', 'status',
18-
'addresses', 'description', 'email', 'homeCountryCode', 'competitionCountryCode', 'photoURL', 'createdAt',
19-
'createdBy','updatedAt','updatedBy']
19+
'addresses', 'description', 'email', 'homeCountryCode', 'competitionCountryCode', 'photoURL', 'maxRating',
20+
'createdAt', 'createdBy','updatedAt','updatedBy']
2021

2122
const INTERNAL_MEMBER_FIELDS = ['newEmail', 'emailVerifyToken', 'emailVerifyTokenDate', 'newEmailVerifyToken',
2223
'newEmailVerifyTokenDate', 'handleSuggest']
@@ -58,11 +59,11 @@ function omitMemberAttributes (currentUser, mb) {
5859
*/
5960
async function getMember (currentUser, handle, query) {
6061
// validate and parse query parameter
61-
const selectFields = helper.parseCommaSeparatedString(query.fields, MEMBER_FIELDS)
62+
const selectFields = helper.parseCommaSeparatedString(query.fields, MEMBER_FIELDS) || MEMBER_FIELDS
6263
// query member from Elasticsearch
6364
const esQuery = {
64-
index: config.ES.ES_INDEX,
65-
type: config.ES.ES_TYPE,
65+
index: config.ES.MEMBER_PROFILE_ES_INDEX,
66+
type: config.ES.MEMBER_PROFILE_ES_TYPE,
6667
size: constants.ES_SEARCH_MAX_SIZE, // use a large size to query all records
6768
body: {
6869
query: {
@@ -80,6 +81,21 @@ async function getMember (currentUser, handle, query) {
8081
} else {
8182
members = _.map(members.hits.hits, '_source')
8283
}
84+
// get the 'maxRating' from stats
85+
if (_.includes(selectFields, 'maxRating')) {
86+
for (let i = 0; i < members.length; i += 1) {
87+
const memberStatsFields = { "fields": "userId,groupId,handleLower,maxRating" }
88+
const memberStats = await statisticsService.getMemberStats(currentUser, members[i].handleLower,
89+
memberStatsFields, false)
90+
if(memberStats[0]) {
91+
if (memberStats[0].hasOwnProperty("maxRating")) {
92+
members[i].maxRating = memberStats[0].maxRating
93+
} else {
94+
members[i].maxRating = {}
95+
}
96+
}
97+
}
98+
}
8399
// clean member fields according to current user
84100
members = cleanMember(currentUser, members)
85101
// select fields

src/services/MemberTraitService.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ async function getTraits (currentUser, handle, query) {
2727
// get member
2828
const member = await helper.getMemberByHandle(handle)
2929
// parse query parameters
30-
const traitIds = helper.parseCommaSeparatedString(query.traitIds, TRAIT_IDS)
30+
const traitIds = helper.parseCommaSeparatedString(query.traitIds, TRAIT_IDS) || TRAIT_IDS
3131
const fields = helper.parseCommaSeparatedString(query.fields, TRAIT_FIELDS) || TRAIT_FIELDS
3232

3333
// query member traits from Elasticsearch
@@ -203,8 +203,7 @@ updateTraits.schema = createTraits.schema
203203
*/
204204
async function removeTraits (currentUser, handle, query) {
205205
// parse trait ids
206-
const traitIds = helper.parseCommaSeparatedString(query.traitIds, TRAIT_IDS)
207-
206+
const traitIds = helper.parseCommaSeparatedString(query.traitIds, TRAIT_IDS) || TRAIT_IDS
208207
const member = await helper.getMemberByHandle(handle)
209208
// check authorization
210209
if (!helper.canManageMember(currentUser, member)) {

src/services/MiscService.js

+13-12
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,21 @@ const MEMBER_FINANCIAL_FIELDS = ['userId', 'amount', 'status', 'createdAt', 'upd
1919
*/
2020
async function getMemberFinancial (currentUser, handle, query) {
2121
// validate and parse query parameter
22-
const fields = helper.parseCommaSeparatedString(query.fields, MEMBER_FINANCIAL_FIELDS)
22+
const fields = helper.parseCommaSeparatedString(query.fields, MEMBER_FINANCIAL_FIELDS) || MEMBER_FINANCIAL_FIELDS
2323
// get member by handle
2424
const member = await helper.getMemberByHandle(handle)
25-
// only admin, M2M or user himself can get financial data
26-
if (!helper.canManageMember(currentUser, member)) {
27-
throw new errors.ForbiddenError('You are not allowed to get financial data of the user.')
28-
}
29-
// get financial data by member user id
30-
let data = await helper.getEntityByHashKey(handle, 'MemberFinancial', 'userId', member.userId, true)
31-
// select fields if provided
32-
if (fields) {
33-
data = _.pick(data, fields)
34-
}
35-
return data
25+
// // only admin, M2M or user himself can get financial data
26+
// if (!helper.canManageMember(currentUser, member)) {
27+
// throw new errors.ForbiddenError('You are not allowed to get financial data of the user.')
28+
// }
29+
// // get financial data by member user id
30+
// let data = await helper.getEntityByHashKey(handle, 'MemberFinancial', 'userId', member.userId, true)
31+
// // select fields if provided
32+
// if (fields) {
33+
// data = _.pick(data, fields)
34+
// }
35+
// return data
36+
return { 'message': 'No Data' }
3637
}
3738

3839
getMemberFinancial.schema = {

0 commit comments

Comments
 (0)