Skip to content

Commit feb670d

Browse files
author
vikasrohit
authored
Merge pull request #38 from topcoder-platform/develop
Release v1.1.0
2 parents b4224e0 + cb0543d commit feb670d

11 files changed

+187
-109
lines changed

config/default.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ module.exports = {
2121
NDA_TERMS_ID: process.env.NDA_TERMS_ID || '0dedac8f-5a1a-4fe7-002f-e1d04dc65b7d',
2222

2323
// The name of the field which corresponds to country value in the message generated by update profile event
24-
PROFILE_UPDATE_EVENT_COUNTRY_FIELD_NAME: process.env.PROFILE_UPDATE_EVENT_COUNTRY_FIELD_NAME || 'payload.competitionCountryCode',
24+
PROFILE_UPDATE_EVENT_COUNTRY_FIELD_NAME: process.env.PROFILE_UPDATE_EVENT_COUNTRY_FIELD_NAME || 'homeCountryCode',
2525

2626
topics: {
2727
// The Kafka topic to which to listen to user terms agreement events

src/common/helper.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,25 @@ async function getHandleByUserId (userId) {
5656
}
5757
}
5858

59+
/**
60+
* This function retrieves the details of the user identified by the given handle
61+
*
62+
* @param {String} handle The handle of the user for whom to get the details
63+
* @returns The details of the user
64+
*
65+
*/
66+
async function getMemberByHandle (handle) {
67+
logger.debug({ component: 'helper', context: 'getMemberByHandle', message: `handle: ${handle}` })
68+
69+
const token = await getM2MToken()
70+
71+
const { body: user } = await request
72+
.get(`${config.MEMBER_API_URL}/${handle}`)
73+
.set('Authorization', `Bearer ${token}`)
74+
75+
return user
76+
}
77+
5978
/**
6079
* Gets the member traits for the given trait id and member handle
6180
*
@@ -168,5 +187,6 @@ module.exports = {
168187
getMemberTraits,
169188
saveMemberTraits,
170189
executeQueryAsync,
171-
hasUserEnteredSkills
190+
hasUserEnteredSkills,
191+
getMemberByHandle
172192
}

src/services/IdVerificationProcessorService.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ async function saveIdVerificationTrait (idVerification) {
9292
const traitsData = {
9393
status: constants.CHECKLIST_STATUS.COMPLETED,
9494
message: constants.CHECKLIST_MESSAGE.SUCCESS,
95-
date: idVerification.verification_date,
95+
date: new Date(idVerification.verification_date).getTime(),
9696
metadata: {
9797
matched_on: idVerification.matched_on,
9898
verification_mode: idVerification.verification_mode

src/services/ProfileCompletionProcessorService.js

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const config = require('config')
1313
/**
1414
* This array contains the list of profile completion metadata object keys that affect the status update
1515
*/
16-
const METADATA_KEYS_FOR_STATUS_UPDATE = ['profile_picture', 'bio', 'skills', 'education', 'work', 'language']
16+
const METADATA_KEYS_FOR_STATUS_UPDATE = ['profile_picture', 'bio', 'skills', 'education', 'work', 'language', 'country']
1717

1818
// The component name to be used when logging messages
1919
const component = 'ProfileCompletionProcessorService'
@@ -27,7 +27,7 @@ async function processProfileUpdateMessage (message) {
2727
// The eventually updated metadata items by the current event message
2828
const updatedMetadataItems = {
2929
bio: !_.isEmpty(_.get(message, 'payload.description')),
30-
country: !_.isEmpty(_.get(message, config.PROFILE_UPDATE_EVENT_COUNTRY_FIELD_NAME))
30+
country: !_.isEmpty(_.get(message, `payload.${config.PROFILE_UPDATE_EVENT_COUNTRY_FIELD_NAME}`))
3131
}
3232

3333
await handleUpdatedProfileCompletionMetadata(message, updatedMetadataItems)
@@ -147,22 +147,11 @@ processProfileTraitRemovalMessage.schema = {
147147
*
148148
* @param {Object} oldChecklist The old checklist object
149149
* @param {Object} updatedMetadataItems The updated metadata objects
150-
* @param {String} handle The user handle
151150
* @returns The new checklist
152151
*/
153-
async function getNewChecklist (oldChecklist, updatedMetadataItems, handle) {
152+
async function getNewChecklist (oldChecklist, updatedMetadataItems) {
154153
// Initialize the aggregated updatedMetadata object
155-
let updatedMetadata
156-
if (!_.get(oldChecklist, 'metadata.skills')) {
157-
// The skills trait was not previously set, we need to re-check from member-api if it is done and update the metadata accordingly
158-
updatedMetadata = _.assign({},
159-
oldChecklist.metadata,
160-
updatedMetadataItems,
161-
{ skills: await helper.hasUserEnteredSkills(handle) })
162-
} else {
163-
// The skills trait was previously set, no need to make the call to member-api for checking again
164-
updatedMetadata = _.assign({}, oldChecklist.metadata, updatedMetadataItems)
165-
}
154+
const updatedMetadata = _.assign({}, oldChecklist.metadata, updatedMetadataItems)
166155

167156
if (_.isEqual(oldChecklist.metadata, updatedMetadata)) {
168157
// Nothing has changed in the metadata, return the result with 'updated' flag set to false
@@ -202,23 +191,14 @@ async function getNewChecklist (oldChecklist, updatedMetadataItems, handle) {
202191
* This function generates the initial structure of the profile completion checklist
203192
*
204193
* @param {Object} metadata The metadata to put in the checklist
205-
* @param {String} handle the user handle
206194
* @returns The initial checklist structure with the the given metadata
207195
*/
208-
async function getInitialChecklist (metadata, handle) {
196+
async function getInitialChecklist (metadata) {
209197
return {
210198
status: CHECKLIST_STATUS.PENDING_AT_USER,
211199
message: CHECKLIST_MESSAGE.PROFILE_IS_INCOMPLETE,
212200
date: new Date().getTime(),
213-
metadata: _.assign({}, {
214-
profile_picture: false,
215-
bio: false,
216-
skills: await helper.hasUserEnteredSkills(handle),
217-
education: false,
218-
work: false,
219-
language: false,
220-
country: false
221-
}, metadata)
201+
metadata: _.assign({}, metadata)
222202
}
223203
}
224204

@@ -232,13 +212,16 @@ async function handleUpdatedProfileCompletionMetadata (message, updatedMetadataI
232212
// Get the user handle from members api
233213
const handle = await helper.getHandleByUserId(_.get(message, 'payload.userId'))
234214

215+
// Check for other updates in user profile and profile traits
216+
const fullyUpdatedMetadata = await getFullyUpdatedMetadata(handle, updatedMetadataItems)
217+
235218
// context used for logging
236219
const context = 'handleUpdatedProfileCompletionMetadata'
237220

238221
logger.debug({
239222
component,
240223
context,
241-
message: `Process profile completion trait: { user: '${handle}', updatedMetadata: ${JSON.stringify(updatedMetadataItems)}}`
224+
message: `Process profile completion trait: { user: '${handle}', updatedMetadata: ${JSON.stringify(fullyUpdatedMetadata)}}`
242225
})
243226

244227
// Get the member Onboarding Checklist traits
@@ -258,7 +241,7 @@ async function handleUpdatedProfileCompletionMetadata (message, updatedMetadataI
258241
isCreate = true
259242
// The member does not have any onboarding checklist trait, we initialize it
260243
body[0].traits.data.push({
261-
[PROFILE_COMPLETION_TRAIT_PROPERTY_NAME]: await getInitialChecklist(updatedMetadataItems, handle)
244+
[PROFILE_COMPLETION_TRAIT_PROPERTY_NAME]: await getInitialChecklist(fullyUpdatedMetadata)
262245
})
263246
} else {
264247
isCreate = false
@@ -271,11 +254,11 @@ async function handleUpdatedProfileCompletionMetadata (message, updatedMetadataI
271254
if (_.isUndefined(body[0].traits.data[0][PROFILE_COMPLETION_TRAIT_PROPERTY_NAME])) {
272255
// There were no traits data for profile completion checklist
273256
// We initialize a new one with the updated metadata by the current event message
274-
body[0].traits.data[0][PROFILE_COMPLETION_TRAIT_PROPERTY_NAME] = await getInitialChecklist(updatedMetadataItems, handle)
257+
body[0].traits.data[0][PROFILE_COMPLETION_TRAIT_PROPERTY_NAME] = await getInitialChecklist(fullyUpdatedMetadata)
275258
} else {
276259
// traits data for profile completion checklist is already there for the user
277260
// We update it based on old checklist data and updated metadata by the current event message
278-
const newChecklist = await getNewChecklist(body[0].traits.data[0][PROFILE_COMPLETION_TRAIT_PROPERTY_NAME], updatedMetadataItems, handle)
261+
const newChecklist = await getNewChecklist(body[0].traits.data[0][PROFILE_COMPLETION_TRAIT_PROPERTY_NAME], fullyUpdatedMetadata)
279262

280263
if (!newChecklist.isUpdated) {
281264
// The checklist was not updated, there is no need to call member-api
@@ -296,7 +279,7 @@ async function handleUpdatedProfileCompletionMetadata (message, updatedMetadataI
296279
logger.debug({
297280
component,
298281
context,
299-
message: `Successfully processed profile completion trait { user: '${handle}', updatedMetadata: ${JSON.stringify(updatedMetadataItems)}}`
282+
message: `Successfully processed profile completion trait { user: '${handle}', updatedMetadata: ${JSON.stringify(fullyUpdatedMetadata)}}`
300283
})
301284
}
302285

@@ -330,6 +313,31 @@ processProfilePictureUploadMessage.schema = {
330313
}).required()
331314
}
332315

316+
/**
317+
* This function retrieves the fully updated metadata items for the member.
318+
* It gets the traits from the member traits API and returns the fully updated metadata with the updated flags
319+
*
320+
* @param {String} handle The member handle
321+
* @param {Object} updatedMetadataItems The updated metadata generated as a result of the event
322+
*/
323+
async function getFullyUpdatedMetadata (handle, updatedMetadataItems) {
324+
const member = await helper.getMemberByHandle(handle)
325+
const existingTraits = await helper.getMemberTraits(handle, _.join(_.keys(TRAITS_TO_PROFILE_COMPLETION_CHECKLIST_METADATA_MAP), ','))
326+
const updateTraitsMetadata = {}
327+
328+
for (const key of _.keys(TRAITS_TO_PROFILE_COMPLETION_CHECKLIST_METADATA_MAP)) {
329+
const trait = _.find(existingTraits, { traitId: key })
330+
updateTraitsMetadata[TRAITS_TO_PROFILE_COMPLETION_CHECKLIST_METADATA_MAP[key]] = _.get(trait, 'traits.data', []).length > 0
331+
}
332+
333+
return _.assign({}, {
334+
profile_picture: !_.isEmpty(_.get(member, 'photoURL')),
335+
bio: !_.isEmpty(_.get(member, 'description')),
336+
skills: await helper.hasUserEnteredSkills(handle),
337+
country: !_.isEmpty(_.get(member, config.PROFILE_UPDATE_EVENT_COUNTRY_FIELD_NAME))
338+
}, updateTraitsMetadata, updatedMetadataItems)
339+
}
340+
333341
module.exports = {
334342
processProfileUpdateMessage,
335343
processCreateOrUpdateProfileTraitMessage,

src/services/TermsAgreementProcessorService.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,8 @@ processMessage.schema = {
102102
payload: Joi.object()
103103
.keys({
104104
userId: Joi.positiveId().required(),
105-
termsOfUseId: Joi.string().uuid().required(),
106-
legacyId: Joi.positiveId(),
107-
created: Joi.date()
108-
})
105+
termsOfUseId: Joi.string().uuid().required()
106+
}).unknown(true)
109107
.required()
110108
})
111109
.required()

test/common/idVerificationTestData.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const idVerificationExistingTraits = [
6666
'data': [
6767
{
6868
'id_verification': {
69-
'date': '2021-07-21',
69+
'date': new Date('2021-07-21').getTime(),
7070
'message': 'success',
7171
'status': 'completed',
7272
'metadata': {

test/common/profileCompletionTestData.js

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,99 @@ const saarixxExistingTraits = [
279279
'updatedAt': 1631785852398,
280280
'createdBy': 'XKMy68LCDT7mwKaUtl17Pgf5u2A7dXYo@clients',
281281
'updatedBy': 'XKMy68LCDT7mwKaUtl17Pgf5u2A7dXYo@clients'
282+
},
283+
{
284+
'userId': saarixxUserId,
285+
'traitId': 'education',
286+
'traits': {
287+
'traitId': 'education',
288+
'data': [
289+
{
290+
'University': 'Kyiv Polytechnic'
291+
}
292+
]
293+
}
294+
},
295+
{
296+
'userId': saarixxUserId,
297+
'traitId': 'work',
298+
'traits': {
299+
'traitId': 'work',
300+
'data': [
301+
{
302+
'Topcoder': 'Software Designer'
303+
}
304+
]
305+
}
282306
}
283307
]
284308

309+
const memberDetails = {
310+
'userId': 40154303,
311+
'handle': 'upbeat',
312+
'handleLower': 'upbeat',
313+
'firstName': 'Atif Ali',
314+
'lastName': 'Siddiqui 12345',
315+
'tracks': [
316+
'DESIGN',
317+
'DEVELOP'
318+
],
319+
'status': 'ACTIVE',
320+
'addresses': [
321+
{
322+
'zip': '560110',
323+
'streetAddr1': 'GM INFINITE ECITY TOWN',
324+
'city': 'Bangalore',
325+
'stateCode': 'Karnataka',
326+
'type': 'HOME'
327+
}
328+
],
329+
'description': 'What goes around comes around',
330+
'email': '[email protected]',
331+
'homeCountryCode': 'IRL',
332+
'competitionCountryCode': 'IRL',
333+
'photoURL': 'https://topcoder-dev-media.s3.amazonaws.com/member/profile/upbeat-1575621848253.png',
334+
'maxRating': {
335+
'rating': 0,
336+
'track': 'DATA_SCIENCE',
337+
'subTrack': [
338+
'SRM'
339+
],
340+
'ratingColor': '#9D9FA0'
341+
},
342+
'createdAt': 1515982240000,
343+
'createdBy': '40154303',
344+
'updatedAt': 1629632394246,
345+
'updatedBy': '40029484'
346+
}
347+
348+
const saarixxSkills = {
349+
'userId': 123,
350+
'handle': 'skills',
351+
'handleLower': 'postmane2e-denis',
352+
'skills': {
353+
'286': {
354+
'hidden': false,
355+
'score': 1888,
356+
'sources': [
357+
'source1',
358+
'source2'
359+
],
360+
'tagName': 'Java'
361+
},
362+
'311': {
363+
'hidden': false,
364+
'tagName': 'Python',
365+
'sources': [
366+
'USER_ENTERED'
367+
],
368+
'score': 90
369+
}
370+
},
371+
'createdAt': 1621895619502,
372+
'updatedAt': 1621895619502
373+
}
374+
285375
module.exports = {
286376
testMethods,
287377
denisSkills,
@@ -290,5 +380,7 @@ module.exports = {
290380
thomasUserId,
291381
thomasExistingTraits,
292382
saarixxExistingTraits,
293-
saarixxUserId
383+
saarixxUserId,
384+
memberDetails,
385+
saarixxSkills
294386
}

test/common/testData.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ const stringFields = ['topic', 'originator', 'mime-type']
6161

6262
const guidFields = ['payload.termsOfUseId']
6363

64-
const positiveIntegerFields = ['payload.userId', 'payload.legacyId']
64+
const positiveIntegerFields = ['payload.userId']
6565

66-
const dateFields = ['timestamp', 'payload.created']
66+
const dateFields = ['timestamp']
6767

6868
const nonExistingUserId = 111111
6969

test/unit/IdVerificationProcessorService.test.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ describe('Topcoder Onboarding Checklist - Id Verification Processor Service Unit
8888
await service.processIdVerification()
8989

9090
assertInfoMessage('Processing id verification')
91-
assertInfoMessage('Successfully Processed id verification')
9291
for (const handle of ['denis', 'upbeat']) {
9392
assertDebugMessage(`Saving id verification member trait for user '${handle}'`)
9493
assertDebugMessage(`Successfully completed saving id verification member trait for user '${handle}'`)
@@ -103,7 +102,6 @@ describe('Topcoder Onboarding Checklist - Id Verification Processor Service Unit
103102
await service.processIdVerification()
104103

105104
assertInfoMessage('Processing id verification')
106-
assertInfoMessage('Successfully Processed id verification')
107105
should.equal(stub.callCount, 1)
108106
assertDebugMessage(`Id verification trait is already set for user 'idVerification', Skipping...!`)
109107
})

0 commit comments

Comments
 (0)