Skip to content

Commit 8e5c9d9

Browse files
author
vikasrohit
authored
Merge pull request #117 from maxceem/feature/skip-notifications-for-customers
Feature/skip notifications for customers
2 parents 3d20925 + 0cb0027 commit 8e5c9d9

17 files changed

+343
-178
lines changed

config/default.js

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,56 +45,57 @@ module.exports = {
4545
AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL,
4646

4747
KAFKA_CONSUMER_RULESETS: {
48-
// key is Kafka topic name, value is array of ruleset which have key as handler function name defined in src/processors/index.js
48+
// key is Kafka topic name, value is array of ruleset which have key as
49+
// handler function name defined in src/processors/index.js
4950
'challenge.notification.events': [
5051
{
5152
handleChallenge: /** topic handler name */
5253
{
5354
type: 'UPDATE_DRAFT_CHALLENGE',
54-
roles: ["Submitter" /** Competitor */, "Copilot", "Reviewer"],
55+
roles: ['Submitter' /** Competitor */, 'Copilot', 'Reviewer'],
5556
notification:
5657
{
5758
id: 0, /** challengeid or projectid */
5859
name: '', /** challenge name */
5960
group: 'Challenge',
60-
title: 'Challenge specification is modified.'
61-
}
62-
}
63-
}
61+
title: 'Challenge specification is modified.',
62+
},
63+
},
64+
},
6465
],
6566
'notifications.autopilot.events': [
6667
{
6768
handleAutoPilot:
6869
{
6970
phaseTypeName: 'Checkpoint Screening',
7071
state: 'START',
71-
roles: ["Copilot", "Reviewer"],
72+
roles: ['Copilot', 'Reviewer'],
7273
notification:
7374
{
7475
id: 0, /** challengeid or projectid */
7576
name: '', /** challenge name */
7677
group: 'Challenge',
77-
title: 'Challenge checkpoint review.'
78-
}
79-
}
80-
}
78+
title: 'Challenge checkpoint review.',
79+
},
80+
},
81+
},
8182
],
8283
'submission.notification.create': [
8384
{
8485
handleSubmission:
8586
{
8687
resource: 'submission',
87-
roles: ["Copilot", "Reviewer"],
88+
roles: ['Copilot', 'Reviewer'],
8889
selfOnly: true /** Submitter only */,
8990
notification:
9091
{
9192
id: 0, /** challengeid or projectid */
9293
name: '', /** challenge name */
9394
group: 'Submission',
94-
title: 'A new submission is uploaded.'
95-
}
96-
}
97-
}
95+
title: 'A new submission is uploaded.',
96+
},
97+
},
98+
},
9899
],
99100
//'notifications.community.challenge.created': ['handleChallengeCreated'],
100101
//'notifications.community.challenge.phasewarning': ['handleChallengePhaseWarning'],

connect/connectNotificationServer.js

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const config = require('./config');
99
const notificationServer = require('../index');
1010
const _ = require('lodash');
1111
const service = require('./service');
12+
const helpers = require('./helpers');
1213
const { BUS_API_EVENT } = require('./constants');
1314
const EVENTS = require('./events-config').EVENTS;
1415
const PROJECT_ROLE_RULES = require('./events-config').PROJECT_ROLE_RULES;
@@ -246,6 +247,83 @@ const getNotificationsForTopicStarter = (eventConfig, topicId) => {
246247
});
247248
};
248249

250+
/**
251+
* Filter members by project roles
252+
*
253+
* @params {Array} List of project roles
254+
* @params {Array} List of project members
255+
*
256+
* @returns {Array} List of objects with user ids
257+
*/
258+
const filterMembersByRoles = (roles, members) => {
259+
let result = [];
260+
261+
roles.forEach(projectRole => {
262+
result = result.concat(
263+
_.filter(members, PROJECT_ROLE_RULES[projectRole])
264+
.map(projectMember => ({
265+
userId: projectMember.userId.toString(),
266+
}))
267+
);
268+
});
269+
270+
return result;
271+
};
272+
273+
/**
274+
* Exclude private posts notification
275+
*
276+
* @param {Object} eventConfig event configuration
277+
* @param {Object} project project details
278+
* @param {Array} tags list of message tags
279+
*
280+
* @return {Promise} resolves to a list of notifications
281+
*/
282+
const getExcludedPrivatePostNotifications = (eventConfig, project, tags) => {
283+
// skip if message is not private or exclusion rule is not configured
284+
if (!_.includes(tags, 'MESSAGES') || !eventConfig.privatePostsForProjectRoles) {
285+
return Promise.resolve([]);
286+
}
287+
288+
const members = _.get(project, 'members', []);
289+
const notifications = filterMembersByRoles(eventConfig.privatePostsForProjectRoles, members);
290+
291+
return Promise.resolve(notifications);
292+
};
293+
294+
/**
295+
* Exclude notifications about posts inside draft phases
296+
*
297+
* @param {Object} eventConfig event configuration
298+
* @param {Object} project project details
299+
* @param {Array} tags list of message tags
300+
*
301+
* @return {Promise} resolves to a list of notifications
302+
*/
303+
const getExcludeDraftPhasesNotifications = (eventConfig, project, tags) => {
304+
// skip is no exclusion rule is configured
305+
if (!eventConfig.draftPhasesForProjectRoles) {
306+
return Promise.resolve([]);
307+
}
308+
309+
const phaseId = helpers.extractPhaseId(tags);
310+
// skip if it is not phase notification
311+
if (!phaseId) {
312+
return Promise.resolve([]);
313+
}
314+
315+
// exclude all user with configured roles if phase is in draft state
316+
return service.getPhase(project.id, phaseId)
317+
.then((phase) => {
318+
if (phase.status === 'draft') {
319+
const members = _.get(project, 'members', []);
320+
const notifications = filterMembersByRoles(eventConfig.draftPhasesForProjectRoles, members);
321+
322+
return Promise.resolve(notifications);
323+
}
324+
});
325+
};
326+
249327
/**
250328
* Exclude notifications using exclude rules of the event config
251329
*
@@ -272,12 +350,17 @@ const excludeNotifications = (notifications, eventConfig, message, data) => {
272350
// and after filter out such notifications from the notifications list
273351
// TODO move this promise all together with `_.uniqBy` to one function
274352
// and reuse it here and in `handler` function
353+
const tags = _.get(message, 'tags', []);
354+
275355
return Promise.all([
276356
getNotificationsForTopicStarter(excludeEventConfig, message.topicId),
277357
getNotificationsForUserId(excludeEventConfig, message.userId),
278-
getNotificationsForMentionedUser(eventConfig, message.postContent),
358+
getNotificationsForMentionedUser(excludeEventConfig, message.postContent),
279359
getProjectMembersNotifications(excludeEventConfig, project),
280360
getTopCoderMembersNotifications(excludeEventConfig),
361+
// these are special exclude rules which are only working for excluding notifications but not including
362+
getExcludedPrivatePostNotifications(excludeEventConfig, project, tags),
363+
getExcludeDraftPhasesNotifications(excludeEventConfig, project, tags),
281364
]).then((notificationsPerSource) => (
282365
_.uniqBy(_.flatten(notificationsPerSource), 'userId')
283366
)).then((excludedNotifications) => {

connect/events-config.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { BUS_API_EVENT } = require('./constants');
55

66
// project member role names
77
const PROJECT_ROLE_OWNER = 'owner';
8+
const PROJECT_ROLE_CUSTOMER = 'customer';
89
const PROJECT_ROLE_COPILOT = 'copilot';
910
const PROJECT_ROLE_MANAGER = 'manager';
1011
const PROJECT_ROLE_MEMBER = 'member';
@@ -13,13 +14,15 @@ const PROJECT_ROLE_ACCOUNT_MANAGER = 'account_manager';
1314
// project member role rules
1415
const PROJECT_ROLE_RULES = {
1516
[PROJECT_ROLE_OWNER]: { role: 'customer', isPrimary: true },
17+
[PROJECT_ROLE_CUSTOMER]: { role: 'customer' },
1618
[PROJECT_ROLE_COPILOT]: { role: 'copilot' },
1719
[PROJECT_ROLE_MANAGER]: { role: 'manager' },
1820
[PROJECT_ROLE_ACCOUNT_MANAGER]: { role: 'account_manager' },
1921
[PROJECT_ROLE_MEMBER]: {},
2022
};
2123

2224
// TopCoder roles
25+
// eslint-disable-next-line no-unused-vars
2326
const ROLE_CONNECT_COPILOT = 'Connect Copilot';
2427
const ROLE_CONNECT_MANAGER = 'Connect Manager';
2528
const ROLE_CONNECT_COPILOT_MANAGER = 'Connect Copilot Manager';
@@ -123,31 +126,53 @@ const EVENTS = [
123126
version: 2,
124127
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
125128
toMentionedUsers: true,
129+
exclude: {
130+
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
131+
},
126132
}, {
127133
type: BUS_API_EVENT.CONNECT.POST.CREATED,
128134
version: 2,
129135
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
130136
toTopicStarter: true,
131137
toMentionedUsers: true,
138+
exclude: {
139+
draftPhasesForProjectRoles: [PROJECT_ROLE_CUSTOMER],
140+
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
141+
},
132142
}, {
133143
type: BUS_API_EVENT.CONNECT.POST.UPDATED,
134144
version: 2,
135145
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
136146
toTopicStarter: true,
137147
toMentionedUsers: true,
148+
exclude: {
149+
draftPhasesForProjectRoles: [PROJECT_ROLE_CUSTOMER],
150+
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
151+
},
138152
}, {
139153
type: BUS_API_EVENT.CONNECT.POST.MENTION,
154+
exclude: {
155+
draftPhasesForProjectRoles: [PROJECT_ROLE_CUSTOMER],
156+
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
157+
},
140158
},
141159
{
142160
type: BUS_API_EVENT.CONNECT.TOPIC.DELETED,
143161
version: 2,
144162
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
145163
toTopicStarter: false,
164+
exclude: {
165+
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
166+
},
146167
},
147168
{
148169
type: BUS_API_EVENT.CONNECT.POST.DELETED,
149170
version: 2,
150171
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
172+
exclude: {
173+
draftPhasesForProjectRoles: [PROJECT_ROLE_CUSTOMER],
174+
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
175+
},
151176
},
152177
{
153178
type: BUS_API_EVENT.CONNECT.PROJECT.LINK_CREATED,

connect/helpers.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
* Helper functions
33
*/
44
const Remarkable = require('remarkable');
5+
const _ = require('lodash');
6+
7+
const PHASE_ID_REGEXP = /phase#(\d+)/;
58

69
/**
710
* Convert markdown into raw draftjs state
@@ -42,7 +45,20 @@ const sanitizeEmail = (email) => {
4245
return '';
4346
};
4447

48+
/**
49+
* Helper method to extract phaseId from tag
50+
*
51+
* @param {Array} tags list of message tags
52+
*
53+
* @returns {String} phase id
54+
*/
55+
const extractPhaseId = (tags) => {
56+
const phaseIds = tags.map((tag) => _.get(tag.match(PHASE_ID_REGEXP), '1', null));
57+
return _.find(phaseIds, (phaseId) => phaseId !== null);
58+
};
59+
4560
module.exports = {
4661
markdownToHTML,
4762
sanitizeEmail,
63+
extractPhaseId,
4864
};

0 commit comments

Comments
 (0)