Skip to content

Community Notifications Changes #183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ workflows:
context : org-global
filters:
branches:
only: [dev, 'hotfix/V5-API-Standards', 'v5-upgrade', 'feature/bulk-notification']
only: [dev, 'bug/rate-limit']
- "build-prod":
context : org-global
filters:
Expand Down
43 changes: 29 additions & 14 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,35 +70,50 @@ module.exports = {
{
phaseTypeName: 'Checkpoint Screening',
state: 'START',
roles: ['Copilot', 'Reviewer'],
roles: ['Primary Screener'],
notification:
{
id: 0, /** challengeid or projectid */
name: '', /** challenge name */
group: 'challenge',
title: 'Challenge checkpoint review.',
title: 'Checkpoint Screening phase is now open. You may now begin screening checkpoint submissions.',
},
},
},
],
'submission.notification.create': [
{
handleSubmission:
}, {
handleAutoPilot:
{
resource: 'submission',
roles: ['Copilot', 'Reviewer'],
selfOnly: true /** Submitter only */,
phaseTypeName: 'Checkpoint Review',
state: 'START',
roles: ['Copilot'],
notification:
{
id: 0, /** challengeid or projectid */
name: '', /** challenge name */
group: 'submission',
title: 'A new submission is uploaded.',
group: 'challenge',
title: 'Checkpoint Review is now open. You may now begin reviewing checkpoint submissions.',
},
},
},
],
'admin.notification.broadcast' : [{
// /** 'submission.notification.create': [
// {
// handleSubmission:
// {
// resource: 'submission',
// roles: ['Copilot', 'Reviewer'],
// selfOnly: true /** Submitter only */,
// notification:
// {
// id: 0, /** challengeid or projectid */
// name: '', /** challenge name */
// group: 'submission',
// title: 'A new submission is uploaded.',
// },
// },
// },
// ],
// */ // issue - https://github.com/topcoder-platform/community-app/issues/4108
'admin.notification.broadcast': [{
handleBulkNotification: {}
}
]
Expand All @@ -112,5 +127,5 @@ module.exports = {
ENABLE_DEV_MODE: process.env.ENABLE_DEV_MODE ? Boolean(process.env.ENABLE_DEV_MODE) : true,
DEV_MODE_EMAIL: process.env.DEV_MODE_EMAIL,
DEFAULT_REPLY_EMAIL: process.env.DEFAULT_REPLY_EMAIL,
ENABLE_HOOK_BULK_NOTIFICATION : process.env.ENABLE_HOOK_BULK_NOTIFICATION || false,
ENABLE_HOOK_BULK_NOTIFICATION: process.env.ENABLE_HOOK_BULK_NOTIFICATION || false,
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6.2",
"topcoder-healthcheck-dropin": "^1.0.3",
"urijs": "^1.19.1",
"winston": "^2.2.0"
"winston": "^2.2.0",
"node-cache": "^5.1.0"
},
"engines": {
"node": "6.x"
Expand Down
66 changes: 52 additions & 14 deletions src/common/broadcastAPIHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ const _ = require('lodash')
const config = require('config')
const request = require('superagent')
const logger = require('./logger')
const m2mAuth = require('tc-core-library-js').auth.m2m;
const m2m = m2mAuth(config);
const m2mAuth = require('tc-core-library-js').auth.m2m
const NodeCache = require('node-cache')

const m2m = m2mAuth(config)
const cache = new NodeCache()

const logPrefix = "BroadcastAPI: "
const cachedTimeInSeconds = 300 //300 seconds

/**
* Helper Function - get m2m token
Expand All @@ -27,6 +31,11 @@ async function getMemberInfo(userId) {
"/members/_search/?" +
`query=userId%3A${userId}` +
`&limit=1`
if (cachedMemberInfo = cache.get(url)) {
return new Promise((resolve, reject) => {
resolve(cachedMemberInfo)
})
}
return new Promise(async function (resolve, reject) {
let memberInfo = []
logger.info(`calling member api ${url} `)
Expand All @@ -37,6 +46,7 @@ async function getMemberInfo(userId) {
}
memberInfo = _.get(res, 'body.result.content')
logger.info(`BCA Memeber API: Feteched ${memberInfo.length} record(s) from member api`)
cache.set(url, memberInfo, cachedTimeInSeconds)
resolve(memberInfo)
} catch (err) {
reject(new Error(`BCA Memeber API: Failed to get member ` +
Expand Down Expand Up @@ -102,12 +112,16 @@ async function callApi(url, machineToken) {

/**
* Helper function - check Skills and Tracks condition
*
* @param {Integer} userId
* @param {Object} bulkMessage
* @param {Object} m memberInfo
*
*/
async function checkUserSkillsAndTracks(userId, bulkMessage) {
async function checkUserSkillsAndTracks(userId, bulkMessage, m) {
try {
const skills = _.get(bulkMessage, 'recipients.skills')
const tracks = _.get(bulkMessage, 'recipients.tracks')
const m = await getMemberInfo(userId)
let skillMatch, trackMatch = false // default
if (skills && skills.length > 0) {
const ms = _.get(m[0], "skills") // get member skills
Expand Down Expand Up @@ -159,25 +173,43 @@ async function checkUserSkillsAndTracks(userId, bulkMessage) {
/**
* Helper function - check group condition
*/
async function checkUserGroup(userId, bulkMessage) {
async function checkUserGroup(userId, bulkMessage, userGroupInfo) {
try {
const excludeGroupSign = '!'
const groups = _.get(bulkMessage, 'recipients.groups')

let flag = false // default
const userGroupInfo = await getUserGroup(userId)
if (groups.length > 0) {
let excludeGroups = []
let includeGroups = []

_.map(groups, (g) => {
if (_.startsWith(g, excludeGroupSign)) {
excludeGroups.push(g)
} else {
includeGroups.push(g)
}
})

if (includeGroups.length > 0) {
_.map(userGroupInfo, (o) => {
// particular group only condition
flag = (_.indexOf(groups, _.get(o, "name")) >= 0) ? true : flag
flag = (_.indexOf(includeGroups, _.get(o, "name")) >= 0) ? true : flag
})
} else { // no group condition means its for `public` no private group
}
if (excludeGroups.length > 0) {
flag = true // default allow for all
_.map(userGroupInfo, (o) => {
// not allow if user is part of any private group
flag = (_.get(o, "privateGroup")) ? false : flag
// not allow if user is part of any private group i.e. excludeGroups
flag = (_.indexOf(excludeGroups, (excludeGroupSign + _.get(o, "name"))) >= 0) ? false : flag
})
logger.info(`public group condition for userId ${userId}` +
` and BC messageId ${bulkMessage.id}, the result is: ${flag}`)
}

if (groups.length === 0) {
flag = true // no restriction
}

return flag
} catch (e) {
throw new Error(`checkUserGroup(): ${e}`)
Expand All @@ -189,12 +221,16 @@ async function checkUserGroup(userId, bulkMessage) {
*
* @param {Integer} userId
* @param {Object} bulkMessage
* @param {Object} memberInfo
* @param {Object} userGroupInfo
*
* @return Promise
*/
async function checkBroadcastMessageForUser(userId, bulkMessage) {
async function checkBroadcastMessageForUser(userId, bulkMessage, memberInfo, userGroupInfo) {
return new Promise(function (resolve, reject) {
Promise.all([
checkUserSkillsAndTracks(userId, bulkMessage),
checkUserGroup(userId, bulkMessage),
checkUserSkillsAndTracks(userId, bulkMessage, memberInfo),
checkUserGroup(userId, bulkMessage, userGroupInfo),
]).then((results) => {
let flag = true // TODO need to be sure about default value
_.map(results, (r) => {
Expand All @@ -214,4 +250,6 @@ async function checkBroadcastMessageForUser(userId, bulkMessage) {

module.exports = {
checkBroadcastMessageForUser,
getMemberInfo,
getUserGroup,
}
2 changes: 1 addition & 1 deletion src/common/tcApiHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ function filterChallengeUsers(usersInfo, filterOnRoles = [], filterOnUsers = [])
}
});
logger.info(`Total roles available in this challenge are: ${rolesAvailable.join(',')}`);
return users;
return _.uniqBy(users, 'userId');
}

/**
Expand Down
20 changes: 16 additions & 4 deletions src/hooks/hookBulkMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,17 @@ async function syncBulkMessageForUser(userId) {
" LEFT OUTER JOIN (SELECT id as refid, bulk_message_id " +
" FROM bulk_message_user_refs AS bmur WHERE bmur.user_id=$1)" +
" AS b ON a.id=b.bulk_message_id WHERE b.refid IS NULL"
let memberInfo = []
let userGroupInfo = []
models.sequelize.query(q, { bind: [userId] })
.then(function (res) {
Promise.all(res[0].map((r) => isBroadCastMessageForUser(userId, r)))
.then(async function (res) {
try {
memberInfo = await api.getMemberInfo(userId)
userGroupInfo = await api.getUserGroup(userId)
} catch (e) {
reject(`${logPrefix} Failed to get member/group info: ${e}`)
}
Promise.all(res[0].map((r) => isBroadCastMessageForUser(userId, r, memberInfo, userGroupInfo)))
.then((results) => {
Promise.all(results.map((o) => {
if (o.result) {
Expand All @@ -94,9 +102,13 @@ async function syncBulkMessageForUser(userId) {
* Check if current user in broadcast recipent group
* @param {Integer} userId
* @param {Object} bulkMessage
* @param {Object} memberInfo
* @param {Object} userGroupInfo
*
* @retun promise
*/
async function isBroadCastMessageForUser(userId, bulkMessage) {
return api.checkBroadcastMessageForUser(userId, bulkMessage)
async function isBroadCastMessageForUser(userId, bulkMessage, memberInfo, userGroupInfo) {
return api.checkBroadcastMessageForUser(userId, bulkMessage, memberInfo, userGroupInfo)
}

/**
Expand Down