Skip to content

Commit feed34d

Browse files
committed
2 parents c155596 + ba605f3 commit feed34d

File tree

7 files changed

+78
-16
lines changed

7 files changed

+78
-16
lines changed

README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
1-
# TOPCODER NOTIFICATIONS SERIES - NOTIFICATIONS SERVER
2-
1+
# TOPCODER NOTIFICATIONS
2+
3+
## Description
4+
This repository hosts the API and processors for enabling notifications in various topcoder apps. Currently it is limited to provide this facility to the [Connect](https://github.com/appirio-tech/connect-app) application. Theoretcially, it is a generic framework and application which could be used for sending and consuming notificaitons by any other topcoder app. In very simple words to send notifications using this application:
5+
6+
1. Send an event to bus
7+
2. Listen that event in tc-notifications
8+
3. There is a config in tc-notifications for each event it want to listen and we can specify rules who are the target users to whom we should send notifications for this event
9+
4. By default it saves all notifications which are generated after parsing the rules specified in step 3 and these are the treated web notifications which we show in the app directly
10+
5. Then there is option to add notification handlers where we get all these notifications one by one and we can process them further for more channels e.g. we send notification emails for each of notification generated
11+
6. When one wants to show the notifications, we use the notifications api (hosted inside the tc-notifications itself as separate service) to fetch notifications, mark notifications read or unread etc.
12+
13+
tc-notifications (as a standard nodejs app) provides generic framework around notifications, it does not have config (used in step 3 of previous list) for specific apps. So, to have config for specific apps, we have to start the notification consumers per app e.g. for connect, we have a folder [`connect`](https://github.com/topcoder-platform/tc-notifications/blob/dev/connect) which hosts start script to start notification consumers with connect specific configuration ([`events-config.js`](https://github.com/topcoder-platform/tc-notifications/blob/dev/connect/events-config.js)). It also adds email notification service which sends emails for notifications as per notification settings (coming from common framework laid by tc-notifications) for the user.
14+
15+
### Steps needed to enable other apps to use the notifications are:
16+
1. Have a separate start up script (in a new folder at the root of the repo) for the concerned app. I would call this as notification consumer/processor.
17+
2. Write [`events-config.js`](https://github.com/topcoder-platform/tc-notifications/blob/dev/connect/events-config.js) (name is not important, we have to read this file in the start up script written in step 1) for app specific notifications
18+
3. Write additional notification services (eg. if you want to send email or slack or any other notification) and add them to startup script
19+
4. Specify a node script in package.json to launch the start up script written in step 1
20+
5. Either add deployment for this new notification consumer/processor in existing deployment script (if you want to host the processor as separate service in the same ECS cluster) or write a new script if you want to keep the deployment separate.
321

422
## Dependencies
523
- nodejs https://nodejs.org/en/ (v6+)

connect/connectNotificationServer.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ notificationServer.setConfig({ LOG_LEVEL: 'debug' });
275275
// logger object used to log in parent thread
276276
// the callback is function(error, userIds), where userIds is an array of user ids to receive notifications
277277
const handler = (topic, message, logger, callback) => {
278+
logger.debug(topic, 'topic');
279+
logger.debug(message, 'message');
278280
const projectId = message.projectId;
279281
if (!projectId) {
280282
return callback(new Error('Missing projectId in the event message.'));
@@ -327,22 +329,30 @@ const handler = (topic, message, logger, callback) => {
327329

328330
// if message has userId such messages will likely need userHandle and user full name
329331
// so let's get it
332+
const ids = [message.initiatorUserId];
333+
logger.debug(message.userId, 'message.userId');
330334
if (message.userId) {
331-
const ids = [message.userId];
332-
return service.getUsersById(ids);
335+
ids.push(message.userId);
333336
}
334-
return [];
337+
return service.getUsersById(ids);
338+
// return [];
335339
}).then((users) => {
340+
logger.debug(users, 'users');
336341
_.map(allNotifications, (notification) => {
337342
notification.version = eventConfig.version;
338343
notification.contents.projectName = project.name;
339344
notification.contents.timestamp = (new Date()).toISOString();
340345
// if found a user then add user handle
341346
if (users.length) {
342-
notification.contents.userHandle = users[0].handle;
343-
notification.contents.userFullName = `${users[0].firstName} ${users[0].lastName}`;
344-
notification.contents.userEmail = users[0].email;
345-
notification.contents.photoURL = users[0].photoURL;
347+
const affectedUser = _.find(users, u => `${u.userId}` === `${message.userId}`);
348+
const initiatorUser = _.find(users, u => `${u.userId}` === `${message.initiatorUserId}`);
349+
if (affectedUser) {
350+
notification.contents.userHandle = affectedUser.handle;
351+
notification.contents.userFullName = `${affectedUser.firstName} ${affectedUser.lastName}`;
352+
notification.contents.userEmail = affectedUser.email;
353+
notification.contents.photoURL = affectedUser.photoURL;
354+
}
355+
notification.contents.initiatorUser = initiatorUser;
346356
}
347357
});
348358
callback(null, allNotifications);

connect/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ module.exports = {
2828
MANAGER_JOINED: 'notifications.connect.project.member.managerJoined',
2929
COPILOT_JOINED: 'notifications.connect.project.member.copilotJoined',
3030
ASSIGNED_AS_OWNER: 'notifications.connect.project.member.assignedAsOwner',
31+
INVITE_CREATED: 'notifications.connect.project.member.invite.created',
32+
INVITE_UPDATED: 'notifications.connect.project.member.invite.updated',
3133
},
3234
PROJECT: {
3335
ACTIVE: 'notifications.connect.project.active',

connect/events-config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ const EVENTS = [
103103
}, {
104104
type: BUS_API_EVENT.CONNECT.MEMBER.MANAGER_JOINED,
105105
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER],
106+
}, {
107+
type: BUS_API_EVENT.CONNECT.MEMBER.INVITE_CREATED,
108+
projectRoles: [],
109+
toUserHandle: true,
106110
},
107111

108112
// Project activity
@@ -256,6 +260,7 @@ const EVENT_BUNDLES = {
256260
BUS_API_EVENT.CONNECT.MEMBER.LEFT,
257261
BUS_API_EVENT.CONNECT.MEMBER.MANAGER_JOINED,
258262
BUS_API_EVENT.CONNECT.MEMBER.REMOVED,
263+
BUS_API_EVENT.CONNECT.MEMBER.INVITE_CREATED,
259264
],
260265
},
261266
PROJECT_PLAN: {

connect/notificationServices/email.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ function handler(topicName, messageJSON, notification) {
205205
const user = users && users.length > 0 ? users[0] : null;
206206
let userEmail = _.get(user, 'email');
207207
if (!userEmail) {
208-
logger.error(`Email not received for user: ${user.id}`);
208+
logger.error(`Email not received for user: ${notification.userId}`);
209209
return;
210210
}
211211
const userStatus = _.get(user, 'status');
@@ -233,6 +233,7 @@ function handler(topicName, messageJSON, notification) {
233233
authorFullName: notification.contents.userFullName,
234234
photoURL: notification.contents.photoURL,
235235
type: notificationType,
236+
emailToAffectedUser: notification.contents.userEmail === userEmail,
236237
},
237238
recipients,
238239
version:"v3",
@@ -281,6 +282,11 @@ function handler(topicName, messageJSON, notification) {
281282
eventMessage.replyTo = `${config.REPLY_EMAIL_PREFIX}+${eventMessage.data.topicId}/${token}@`
282283
+ config.REPLY_EMAIL_DOMAIN;
283284
}
285+
let requiresImmediateAttention = false;
286+
if (BUS_API_EVENT.CONNECT.MEMBER.INVITE_CREATED === notificationType) {
287+
requiresImmediateAttention = true;
288+
}
289+
284290

285291
if (messageJSON.fileName) {
286292
eventMessage.data.fileName = messageJSON.fileName;
@@ -291,12 +297,25 @@ function handler(topicName, messageJSON, notification) {
291297
bundlePeriod = bundlePeriod && bundlePeriod.trim().length > 0 ? bundlePeriod : null;
292298
// if bundling is not explicitly set and the event is not a messaging event, assume bundling enabled
293299
if (!bundlePeriod && !messagingEvent) {
300+
// finds the event category for the notification type
301+
let eventBundleCategory = _.findKey(EVENT_BUNDLES, b => b.types && b.types.indexOf(notificationType) !== -1);
302+
if (eventBundleCategory) {
303+
const eventBundle = EVENT_BUNDLES[eventBundleCategory];
304+
// if we find the event category for the notification, use the bundle settings from the first event
305+
if (eventBundle && eventBundle.types && eventBundle.types.length) {
306+
const firstEvtInBundle = eventBundle.types[0];
307+
const firstEvtBundleSettingPath = `notifications['${firstEvtInBundle}'].${SETTINGS_EMAIL_SERVICE_ID}.bundlePeriod`
308+
let firstEvtBundlePeriod = _.get(settings, firstEvtBundleSettingPath);
309+
bundlePeriod = firstEvtBundlePeriod
310+
logger.debug('Assuming bundle period of first event in the event category=>', bundlePeriod);
311+
}
312+
}
294313
// if bundle period is not set, assume it to be daily for default case
295314
bundlePeriod = !bundlePeriod ? 'daily' : bundlePeriod;
296315
}
297316
logger.debug('bundlePeriod=>', bundlePeriod);
298317

299-
if (bundlePeriod && "immediately" !== bundlePeriod) {
318+
if (bundlePeriod && "immediately" !== bundlePeriod && !requiresImmediateAttention) {
300319
if (!SCHEDULED_EVENT_PERIOD[bundlePeriod]) {
301320
throw new Error(`User's '${notification.userId}' setting for service`
302321
+ ` '${SETTINGS_EMAIL_SERVICE_ID}' option 'bundlePeriod' has unsupported value '${bundlePeriod}'.`);

emails/src/partials/invites.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<td class="comment-box"><img src="https://image.ibb.co/etWM7J/comment.jpg" alt="IMG"></td>
1111
<td class="empty-child-two"></td>
1212
<td>
13-
<strong>Project invitation</a>.
13+
<strong>{{title}}</a>.
1414
</td>
1515
</tr>
1616
</table>
@@ -29,7 +29,8 @@
2929
<td class="main-td">
3030
<table class="main-child">
3131
<tr>
32-
<td>You are invited to join the <strong>{{projectName}}</strong> on Topcoder Connect</td>
32+
<td class="empty-child-one"></td>
33+
<td>You have been invited to a project by {{initiator.firstName}} {{initiator.lastName}}. To join, please register with Topcoder.</td>
3334
</tr>
3435
</table>
3536
</td>
@@ -49,8 +50,8 @@
4950
<tr>
5051
<td class="empty-child-one"></td>
5152
<td class="second-child" align="center">
52-
<a href="https://accounts.topcoder.com/connect/registration?retUrl={{@root.connectURL}}/projects/{{projectId}}">
53-
Register a Topcoder account to join the project
53+
<a href="{{@root.accountsAppURL}}/connect/registration?retUrl={{@root.connectURL}}/projects/{{projectId}}">
54+
Register
5455
</a>
5556
</td>
5657
<td class="empty-child-one"></td>

emails/src/partials/project-team.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,21 @@
4545
<strong>{{userFullName}}</strong> joined the project as Manager
4646
{{/if}}
4747
{{#if [notifications.connect.project.member.removed]}}
48-
<strong>{{userFullName}}</strong> left the project
48+
{{#if [emailToAffectedUser]}}
49+
You are removed from the project
50+
{{else}}
51+
<strong>{{userFullName}}</strong> left the project
52+
{{/if}}
4953
{{/if}}
5054
{{#if [notifications.connect.project.member.left]}}
5155
<strong>{{userFullName}}</strong> left the project
5256
{{/if}}
5357
{{#if [notifications.connect.project.member.joined]}}
5458
<strong>{{userFullName}}</strong> joined the project
5559
{{/if}}
60+
{{#if [notifications.connect.project.member.invite.created]}}
61+
Hi <strong>{{userFullName}}</strong>, you are invited to join the project {{projectName}}. Please click on the button ("View project on Connect") below to join.
62+
{{/if}}
5663
</td>
5764
</tr>
5865
</table>

0 commit comments

Comments
 (0)