Skip to content

Commit f5b4fc7

Browse files
author
sachin-maheshwari
authored
Merge pull request #181 from topcoder-platform/dev
Broadcast notification feature
2 parents c54be52 + 6a139ec commit f5b4fc7

File tree

13 files changed

+531
-11
lines changed

13 files changed

+531
-11
lines changed

.circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ workflows:
102102
context : org-global
103103
filters:
104104
branches:
105-
only: [dev, 'hotfix/V5-API-Standards', 'v5-upgrade', 'feature/platform-filtering']
105+
only: [dev, 'hotfix/V5-API-Standards', 'v5-upgrade', 'feature/bulk-notification']
106106
- "build-prod":
107107
context : org-global
108108
filters:

config/default.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ module.exports = {
5858
{
5959
id: 0, /** challengeid or projectid */
6060
name: '', /** challenge name */
61-
group: 'Challenge',
61+
group: 'challenge',
6262
title: 'Challenge specification is modified.',
6363
},
6464
},
@@ -75,7 +75,7 @@ module.exports = {
7575
{
7676
id: 0, /** challengeid or projectid */
7777
name: '', /** challenge name */
78-
group: 'Challenge',
78+
group: 'challenge',
7979
title: 'Challenge checkpoint review.',
8080
},
8181
},
@@ -92,12 +92,16 @@ module.exports = {
9292
{
9393
id: 0, /** challengeid or projectid */
9494
name: '', /** challenge name */
95-
group: 'Submission',
95+
group: 'submission',
9696
title: 'A new submission is uploaded.',
9797
},
9898
},
9999
},
100100
],
101+
'admin.notification.broadcast' : [{
102+
handleBulkNotification: {}
103+
}
104+
]
101105
//'notifications.community.challenge.created': ['handleChallengeCreated'],
102106
//'notifications.community.challenge.phasewarning': ['handleChallengePhaseWarning'],
103107
},
@@ -108,4 +112,5 @@ module.exports = {
108112
ENABLE_DEV_MODE: process.env.ENABLE_DEV_MODE ? Boolean(process.env.ENABLE_DEV_MODE) : true,
109113
DEV_MODE_EMAIL: process.env.DEV_MODE_EMAIL,
110114
DEFAULT_REPLY_EMAIL: process.env.DEFAULT_REPLY_EMAIL,
115+
ENABLE_HOOK_BULK_NOTIFICATION : process.env.ENABLE_HOOK_BULK_NOTIFICATION || false,
111116
};

emails/src/partials/project-team.html

+1-5
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,7 @@
5858
<strong>{{userFullName}}</strong> joined the project
5959
{{/if}}
6060
{{#if [connect.notification.project.member.invite.created]}}
61-
{{#if [isSSO]}}
62-
Hi <strong>{{userFullName}}</strong>, you are invited to join the project {{projectName}}. Please use the link below to sign in and join the project.
63-
{{else}}
64-
Hi <strong>{{userFullName}}</strong>, you are invited to join the project {{projectName}}. Please click on the button ("View project on Connect") below to join.
65-
{{/if}}
61+
Hi <strong>{{userFullName}}</strong>, you are invited to join the project {{projectName}}. Please click on the button ("Join Project") below to join.
6662
{{/if}}
6763
{{#if [connect.notification.project.member.invite.requested]}}
6864
You are requested to add <strong>{{userFullName}}</strong> as a copilot

src/common/broadcastAPIHelper.js

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/**
2+
* Broadcast: API calling helper
3+
*/
4+
5+
const _ = require('lodash')
6+
const config = require('config')
7+
const request = require('superagent')
8+
const logger = require('./logger')
9+
const m2mAuth = require('tc-core-library-js').auth.m2m;
10+
const m2m = m2mAuth(config);
11+
12+
const logPrefix = "BroadcastAPI: "
13+
14+
/**
15+
* Helper Function - get m2m token
16+
*/
17+
async function getM2MToken() {
18+
return m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET)
19+
}
20+
21+
/**
22+
* Helper Function - get member profile
23+
* @param {Integer} userId
24+
*/
25+
async function getMemberInfo(userId) {
26+
const url = config.TC_API_V3_BASE_URL +
27+
"/members/_search/?" +
28+
`query=userId%3A${userId}` +
29+
`&limit=1`
30+
return new Promise(async function (resolve, reject) {
31+
let memberInfo = []
32+
logger.info(`calling member api ${url} `)
33+
try {
34+
const res = await request.get(url)
35+
if (!_.get(res, 'body.result.success')) {
36+
reject(new Error(`BCA Memeber API: Failed to get member detail for user id ${userId}`))
37+
}
38+
memberInfo = _.get(res, 'body.result.content')
39+
logger.info(`BCA Memeber API: Feteched ${memberInfo.length} record(s) from member api`)
40+
resolve(memberInfo)
41+
} catch (err) {
42+
reject(new Error(`BCA Memeber API: Failed to get member ` +
43+
`api detail for user id ${userId}, ${err}`))
44+
}
45+
46+
})
47+
}
48+
49+
/**
50+
* Helper Function - get user group
51+
* @param {Integer} userId
52+
*/
53+
async function getUserGroup(userId) {
54+
try {
55+
const machineToken = await getM2MToken()
56+
if (machineToken.length <= 0) {
57+
return (new Error(`BCA Group API: fecthing m2m token failed for ${userId}`))
58+
}
59+
let nextPage
60+
let res
61+
let url
62+
let page = 1
63+
let groupInfo = []
64+
const perPage = 100
65+
do {
66+
url = config.TC_API_V5_BASE_URL +
67+
`/groups/?memberId=${userId}&membershipType=user` +
68+
`&page=${page}&perPage=${perPage}`
69+
res = await callApi(url, machineToken)
70+
let resStatus = _.get(res, 'res.statusCode')
71+
if (resStatus != 200) {
72+
throw new Error(`BCA Group API: Failed for user id ${userId},` +
73+
` response status ${resStatus}`)
74+
}
75+
let data = _.get(res, 'body')
76+
groupInfo = groupInfo.concat(data)
77+
nextPage = _.get(res, 'header.x-next-page')
78+
page = nextPage
79+
} while (nextPage)
80+
logger.info(`BCA Group API: Feteched ${groupInfo.length} record(s) from group api`)
81+
return groupInfo
82+
} catch (e) {
83+
logger.error(`BCA: Error calling group api : ${e}`)
84+
throw new Error(`getUserGroup() : ${e}`)
85+
}
86+
}
87+
88+
/**
89+
*
90+
* @param {String} url
91+
* @param {String} machineToken
92+
*/
93+
async function callApi(url, machineToken) {
94+
try {
95+
logger.info(`calling api url ${url}`)
96+
return request.get(url).set('Authorization', `Bearer ${machineToken}`)
97+
} catch (e) {
98+
logger.error(`Error in calling URL ${url}, ${e}`)
99+
throw new Error(`callApi() : ${e}`)
100+
}
101+
}
102+
103+
/**
104+
* Helper function - check Skills and Tracks condition
105+
*/
106+
async function checkUserSkillsAndTracks(userId, bulkMessage) {
107+
try {
108+
const skills = _.get(bulkMessage, 'recipients.skills')
109+
const tracks = _.get(bulkMessage, 'recipients.tracks')
110+
const m = await getMemberInfo(userId)
111+
let skillMatch, trackMatch = false // default
112+
if (skills && skills.length > 0) {
113+
const ms = _.get(m[0], "skills") // get member skills
114+
const memberSkills = []
115+
skillMatch = false
116+
_.map(ms, (o) => {
117+
memberSkills.push(_.get(o, 'name').toLowerCase())
118+
})
119+
_.map(skills, (s) => {
120+
if (_.indexOf(memberSkills, s.toLowerCase()) >= 0) {
121+
skillMatch = true
122+
logger.info(`BroadcastMessageId: ${bulkMessage.id},` +
123+
` '${s}' skill matached for user id ${userId}`)
124+
}
125+
})
126+
} else {
127+
skillMatch = true // no condition, means allow for all
128+
}
129+
130+
//
131+
if (tracks.length > 0) {
132+
trackMatch = false
133+
const uDevChallenges = _.get(m[0], "stats[0].DEVELOP.challenges")
134+
const uDesignChallenges = _.get(m[0], "stats[0].DESIGN.challenges")
135+
const uDSChallenges = _.get(m[0], "stats[0].DATA_SCIENCE.challenges")
136+
_.map(tracks, (t) => {
137+
/**
138+
* checking if user participated in specific challenges
139+
*/
140+
let key = t.toLowerCase()
141+
if (key === "develop") {
142+
trackMatch = uDevChallenges > 0 ? true : trackMatch
143+
} else if (key === "design") {
144+
trackMatch = uDesignChallenges > 0 ? true : trackMatch
145+
} else if (key === "data_science") {
146+
trackMatch = uDSChallenges > 0 ? true : trackMatch
147+
}
148+
})
149+
} else {
150+
trackMatch = true // no condition, means allow for all
151+
}
152+
const flag = (skillMatch && trackMatch) ? true : false
153+
return flag
154+
} catch (e) {
155+
throw new Error(`checkUserSkillsAndTracks() : ${e}`)
156+
}
157+
}
158+
159+
/**
160+
* Helper function - check group condition
161+
*/
162+
async function checkUserGroup(userId, bulkMessage) {
163+
try {
164+
const groups = _.get(bulkMessage, 'recipients.groups')
165+
let flag = false // default
166+
const userGroupInfo = await getUserGroup(userId)
167+
if (groups.length > 0) {
168+
_.map(userGroupInfo, (o) => {
169+
// particular group only condition
170+
flag = (_.indexOf(groups, _.get(o, "name")) >= 0) ? true : flag
171+
})
172+
} else { // no group condition means its for `public` no private group
173+
flag = true // default allow for all
174+
_.map(userGroupInfo, (o) => {
175+
// not allow if user is part of any private group
176+
flag = (_.get(o, "privateGroup")) ? false : flag
177+
})
178+
logger.info(`public group condition for userId ${userId}` +
179+
` and BC messageId ${bulkMessage.id}, the result is: ${flag}`)
180+
}
181+
return flag
182+
} catch (e) {
183+
throw new Error(`checkUserGroup(): ${e}`)
184+
}
185+
}
186+
187+
/**
188+
* Main Function - check if broadcast message is for current user or not
189+
*
190+
* @param {Integer} userId
191+
* @param {Object} bulkMessage
192+
*/
193+
async function checkBroadcastMessageForUser(userId, bulkMessage) {
194+
return new Promise(function (resolve, reject) {
195+
Promise.all([
196+
checkUserSkillsAndTracks(userId, bulkMessage),
197+
checkUserGroup(userId, bulkMessage),
198+
]).then((results) => {
199+
let flag = true // TODO need to be sure about default value
200+
_.map(results, (r) => {
201+
flag = !r ? false : flag // TODO recheck condition
202+
})
203+
logger.info(`BCA: messageId: ${bulkMessage.id} Final recipient` +
204+
` condition result is: ${flag} for userId ${userId}`)
205+
resolve({
206+
record: bulkMessage,
207+
result: flag
208+
})
209+
}).catch((err) => {
210+
reject(`${logPrefix} got issue in checking recipient condition. ${err}`)
211+
})
212+
}) // promise end
213+
}
214+
215+
module.exports = {
216+
checkBroadcastMessageForUser,
217+
}

0 commit comments

Comments
 (0)