Skip to content

Commit d421204

Browse files
committed
Merge branch 'develop' into feature/bulk-milestone-updates
# Conflicts: # package-lock.json
2 parents bb75e22 + 9666d6f commit d421204

11 files changed

+160
-37
lines changed

migrations/elasticsearch_sync.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,7 @@ function getRequestBody (indexName) {
227227
type: 'string'
228228
},
229229
handle: {
230-
type: 'string',
231-
index: 'not_analyzed'
230+
type: 'string'
232231
},
233232
id: {
234233
type: 'long'

src/common/helper.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,50 @@ async function getMemberDetailsByUserIds (userIds) {
165165
}
166166
}
167167

168+
/**
169+
* Populate member with user details
170+
*
171+
* @param {Object} member the member object
172+
* @returns {Object} the member object with details
173+
*/
174+
async function populateMemberWithUserDetails (member) {
175+
try {
176+
const membersDetails = await getMemberDetailsByUserIds([member.userId])
177+
const memberDetails = membersDetails[0]
178+
if (memberDetails) {
179+
logger.debug(`Successfully got user details for member (userId:${member.userId})`)
180+
return _.merge(member, _.pick(memberDetails, 'handle', 'firstName', 'lastName', 'email'))
181+
} else {
182+
throw new Error(`Didn't fine user details for member (userId:${member.userId})`)
183+
}
184+
} catch (err) {
185+
logger.error(`Cannot populate member (userId:${member.userId}) with user details.`)
186+
logger.debug(`Error during populating member (userId:${member.userId}) with user details`, err)
187+
return member
188+
}
189+
}
190+
191+
/**
192+
* Reusable method to generate a function which would remove invite from the project ES document.
193+
*
194+
* @param {Object} message invite update or delete message
195+
*/
196+
const removeInvitePromise = message => async (doc) => {
197+
// now merge the updated changes and re-index the document
198+
const invites = _.isArray(doc._source.invites) ? doc._source.invites : []
199+
const removedInvites = _.remove(invites, { id: message.id })
200+
if (!removedInvites.length) {
201+
throw new Error(`Invite with id "${message.id}" is not found and not removed.`)
202+
}
203+
return _.assign(doc._source, { invites })
204+
}
205+
168206
module.exports = {
169207
getESClient,
170208
updateProjectESPromise,
171209
updateTimelineESPromise,
172210
updateMetadadaESPromise,
173-
getMemberDetailsByUserIds
211+
getMemberDetailsByUserIds,
212+
populateMemberWithUserDetails,
213+
removeInvitePromise
174214
}

src/services/ProcessorServiceProject.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ function createSchema () {
9393
* @return {Promise} promise result
9494
*/
9595
async function create (message) {
96+
const member = await helper.populateMemberWithUserDetails(message.members[0])
97+
message.members = [member]
98+
9699
await client.create({
97100
index: config.get('esConfig.ES_PROJECT_INDEX'),
98101
type: config.get('esConfig.ES_TYPE'),

src/services/ProcessorServiceProjectMember.js

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,7 @@ async function create (message) {
5353
async function updateDocPromise (doc) {
5454
const members = _.isArray(doc._source.members) ? doc._source.members : []
5555
const existingMemberIndex = _.findIndex(members, p => p.id === message.id)// if member does not exists already
56-
let member = message
57-
58-
// try to populate member with user details
59-
// the code should move on, as it's not critical and is only used for searching at the moment
60-
try {
61-
const membersDetails = await helper.getMemberDetailsByUserIds([message.userId])
62-
const memberDetails = membersDetails[0]
63-
if (memberDetails) {
64-
member = _.merge(message, _.pick(memberDetails, 'handle', 'firstName', 'lastName', 'email'))
65-
logger.debug(`Successfully got user details for member (userId:${message.userId})`)
66-
} else {
67-
throw new Error(`Didn't fine user details for member (userId:${message.userId})`)
68-
}
69-
} catch (err) {
70-
logger.error(`Cannot populate member (userId:${message.userId}) with user details.`)
71-
logger.debug(`Error during populating member (userId:${message.userId}) with user details`, err)
72-
}
56+
const member = await helper.populateMemberWithUserDetails(message)
7357

7458
if (existingMemberIndex === -1) {
7559
members.push(member)
@@ -78,6 +62,35 @@ async function create (message) {
7862
// from the database
7963
members.splice(existingMemberIndex, 1, member)
8064
}
65+
66+
// sometimes we have issue that when member accepts invitation the invitation is somehow
67+
// is not removed from the ES, so here we are making sure that invite is removed when we are adding member
68+
try { // make sure that this logic never cause an error in member adding process
69+
const invites = _.filter(doc._source.invites, (invite) => (
70+
invite.email === member.email || invite.userId === member.userId
71+
))
72+
73+
if (invites.length > 0) {
74+
logger.warn(`There are ${invites.length} invite(s) are not yet removed` +
75+
` for member.id: ${member.id} member.userId: ${member.userId}.`)
76+
77+
for (let i = 0; i < invites.length; i++) {
78+
const invite = invites[i]
79+
logger.debug(`Removing invite.id: ${invite.id} for member.id: ${member.id} member.userId: ${member.userId}.`)
80+
try {
81+
const message = { id: invite.id }
82+
const updateDocHandler = helper.removeInvitePromise(message)
83+
await updateDocHandler(doc)
84+
logger.debug(`Successfully removed invite.id: ${invite.id}.`)
85+
} catch (err) {
86+
logger.error(`Failed removing invite.id: ${invite.id}. ${err}`)
87+
}
88+
}
89+
}
90+
} catch (err) {
91+
logger.error(`Error during removing existent invites for added member: ${err}`)
92+
}
93+
8194
return _.assign(doc._source, { members })
8295
}
8396

src/services/ProcessorServiceProjectMemberInvite.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ function createSchema () {
2222
.email()
2323
.optional()
2424
.allow(null),
25+
hashEmail: Joi.string().optional().allow(null),
2526
status: Joi.any()
2627
.valid(_.values(INVITE_STATUS))
2728
.required()
@@ -58,27 +59,33 @@ create.schema = {
5859
* @return {Promise} promise result
5960
*/
6061
async function update (message) {
61-
// handle ES Update
62-
async function updateDocPromise (doc) {
63-
// now merge the updated changes and reindex the document
64-
const invites = _.isArray(doc._source.invites) ? doc._source.invites : []
65-
_.remove(invites, invite => (!!message.email && invite.email === message.email) ||
66-
(!!message.userId && invite.userId === message.userId))
67-
return _.assign(doc._source, { invites })
68-
}
69-
70-
await helper.updateProjectESPromise(message.projectId, updateDocPromise)
62+
await helper.updateProjectESPromise(message.projectId, helper.removeInvitePromise(message))
7163
logger.debug(`Member invite updated successfully in elasticsearch index, (memberInviteId: ${message.id})`)
7264
}
7365

7466
update.schema = {
7567
message: createSchema()
7668
}
7769

70+
/**
71+
* Delete message in Elasticsearch.
72+
* @param {Object} message the deleted message
73+
* @return {Promise} promise result
74+
*/
75+
async function deleteMessage (message) {
76+
await helper.updateProjectESPromise(message.projectId, helper.removeInvitePromise(message))
77+
logger.debug(`Member invite deleted successfully in elasticsearch index, (memberInviteId: ${message.id})`)
78+
}
79+
80+
deleteMessage.schema = {
81+
message: createSchema()
82+
}
83+
7884
// Exports
7985
module.exports = {
8086
create,
81-
update
87+
update,
88+
deleteMessage
8289
}
8390

8491
logger.buildService(module.exports)

test/common/testData.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ const projectMemberInviteCreatedMessage = require(
100100
const projectMemberInviteUpdatedMessage = require(
101101
'../data/project.member.invite/project.action.update.json'
102102
)
103+
const projectMemberInviteDeletedMessage = require(
104+
'../data/project.member.invite/project.action.delete.json'
105+
)
103106
const orgConfigCreatedMessage = require('../data/project.orgConfig/project.action.create.json')
104107
const orgConfigUpdatedMessage = require('../data/project.orgConfig/project.action.update.json')
105108
const orgConfigDeletedMessage = require('../data/project.orgConfig/project.action.delete.json')
@@ -202,6 +205,7 @@ module.exports = {
202205
orgConfigDeletedMessage,
203206
projectMemberInviteUpdatedMessage,
204207
projectMemberInviteCreatedMessage,
208+
projectMemberInviteDeletedMessage,
205209
projectMemberUpdatedMessage,
206210
projectMemberCreatedMessage,
207211
projectMemberDeletedMessage,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"topic":"project.action.create","originator":"project-api","timestamp":"2019-06-21T04:58:12.671Z","mime-type":"application/json","payload":{"resource":"project.member.invite","createdAt":"2019-06-21T04:58:12.594Z","updatedAt":"2019-06-21T04:58:12.594Z","id":1,"projectId":1,"role":"customer","status":"pending","createdBy":40051334,"updatedBy":40051334,"userId":40051331,"email":null,"deletedAt":null,"deletedBy":null}}
1+
{"topic":"project.action.create","originator":"project-api","timestamp":"2019-06-21T04:58:12.671Z","mime-type":"application/json","payload":{"resource":"project.member.invite","createdAt":"2019-06-21T04:58:12.594Z","updatedAt":"2019-06-21T04:58:12.594Z","id":1,"projectId":1,"role":"customer","status":"pending","createdBy":40051334,"updatedBy":40051334,"userId":40051331,"email":"test@topcoder.com","hashEmail":"dummy_value","deletedAt":null,"deletedBy":null}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"topic":"project.action.update","originator":"project-api","timestamp":"2019-06-21T04:59:38.562Z","mime-type":"application/json","payload":{"resource":"project.member.invite","status":"canceled","userId":40051331,"email":"test@topcoder.com","hashEmail":"dummy_value","id":1,"projectId":1}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"topic":"project.action.update","originator":"project-api","timestamp":"2019-06-21T04:59:38.562Z","mime-type":"application/json","payload":{"resource":"project.member.invite","status":"pending","userId":40051331,"email":"test@topcoder.com","id":1,"projectId":1}}
1+
{"topic":"project.action.update","originator":"project-api","timestamp":"2019-06-21T04:59:38.562Z","mime-type":"application/json","payload":{"resource":"project.member.invite","status":"pending","userId":40051331,"email":"test@topcoder.com","hashEmail":"dummy_value","id":1,"projectId":1}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"topic":"project.action.create","originator":"project-api","timestamp":"2019-06-21T04:45:18.165Z","mime-type":"application/json","payload":{"resource":"project.member","createdAt":"2019-06-21T04:45:18.036Z","updatedAt":"2019-06-21T04:45:18.038Z","isPrimary":false,"id":1,"projectId":1,"role":"manager","userId":40051334,"createdBy":40051334,"updatedBy":40051334,"deletedAt":null,"deletedBy":null}}
1+
{"topic":"project.action.create","originator":"project-api","timestamp":"2019-06-21T04:45:18.165Z","mime-type":"application/json","payload":{"resource":"project.member","createdAt":"2019-06-21T04:45:18.036Z","updatedAt":"2019-06-21T04:45:18.038Z","isPrimary":false,"id":1,"projectId":1,"role":"manager","userId":40051331,"createdBy":40051334,"updatedBy":40051334,"deletedAt":null,"deletedBy":null}}

test/e2e/processor.project.index.test.js

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const {
2323
projectMemberInviteId,
2424
projectMemberInviteUpdatedMessage,
2525
projectMemberInviteCreatedMessage,
26+
projectMemberInviteDeletedMessage,
2627
projectMemberUpdatedMessage,
2728
projectMemberCreatedMessage,
2829
projectMemberDeletedMessage,
@@ -831,6 +832,25 @@ describe('TC Project Member Topic Tests', () => {
831832
_.keys(_.omit(projectMemberCreatedMessage.payload, ['resource'])))
832833
})
833834

835+
it('create project member message - already exists - remove not removed invite', async () => {
836+
// let's say we still have invite
837+
await ProcessorService.create(projectMemberInviteCreatedMessage)
838+
let data = await testHelper.getProjectESData(projectId)
839+
// make sure that invite is there
840+
testHelper.expectObj(_.find(data.invites, { id: projectMemberInviteId }), projectMemberInviteCreatedMessage.payload,
841+
_.keys(_.omit(projectMemberInviteCreatedMessage.payload, ['resource'])))
842+
843+
// and we are adding a member (but invite is still there as we created above)
844+
await ProcessorService.create(projectMemberCreatedMessage)
845+
data = await testHelper.getProjectESData(projectId)
846+
847+
// check that member has been added
848+
testHelper.expectObj(_.find(data.members, { id: projectMemberId }), projectMemberCreatedMessage.payload,
849+
_.keys(_.omit(projectMemberCreatedMessage.payload, ['resource'])))
850+
// and at the same time the invite for the member has been removed
851+
expect(_.find(data.invites, { id: projectMemberInviteId })).to.be.an('undefined')
852+
})
853+
834854
it('update project member message', async () => {
835855
await ProcessorService.update(projectMemberUpdatedMessage)
836856
const data = await testHelper.getProjectESData(projectId)
@@ -886,9 +906,45 @@ describe('TC Project Member Invite Topic Tests', () => {
886906
it('update project member invite message - not found', async () => {
887907
const message = _.cloneDeep(projectMemberInviteUpdatedMessage)
888908
message.payload.id = notFoundId
889-
await ProcessorService.update(message)
890-
const data = await testHelper.getProjectESData(projectId)
891-
expect(_.find(data.invites, { id: notFoundId })).to.be.an('undefined')
909+
try {
910+
await ProcessorService.create(projectMemberInviteCreatedMessage)
911+
await ProcessorService.update(message)
912+
} catch (err) {
913+
logger.logFullError(err)
914+
expect(err).to.exist // eslint-disable-line
915+
expect(err.name).to.equal('Error')
916+
const msg = 'not found and not removed.'
917+
expect(err.message.indexOf(msg) >= 0).to.equal(true)
918+
return
919+
}
920+
throw new Error('There should be not found error.')
921+
})
922+
923+
it('delete project member invite message', async () => {
924+
await ProcessorService.create(projectMemberInviteCreatedMessage)
925+
let data = await testHelper.getProjectESData(projectId)
926+
testHelper.expectObj(_.find(data.invites, { id: projectMemberInviteId }), projectMemberInviteCreatedMessage.payload,
927+
_.keys(_.omit(projectMemberInviteCreatedMessage.payload, ['resource'])))
928+
await ProcessorService.deleteMessage(projectMemberInviteDeletedMessage)
929+
data = await testHelper.getProjectESData(projectId)
930+
expect(_.find(data.invites, { id: projectMemberInviteId })).to.be.an('undefined')
931+
})
932+
933+
it('delete project member invite message - not found', async () => {
934+
const message = _.cloneDeep(projectMemberInviteDeletedMessage)
935+
message.payload.id = notFoundId
936+
try {
937+
await ProcessorService.create(projectMemberInviteCreatedMessage)
938+
await ProcessorService.deleteMessage(message)
939+
} catch (err) {
940+
logger.logFullError(err)
941+
expect(err).to.exist // eslint-disable-line
942+
expect(err.name).to.equal('Error')
943+
const msg = 'not found and not removed.'
944+
expect(err.message.indexOf(msg) >= 0).to.equal(true)
945+
return
946+
}
947+
throw new Error('There should be not found error.')
892948
})
893949

894950
it('test logger', async () => {

0 commit comments

Comments
 (0)