Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 0b8e801

Browse files
authored
Merge pull request #66 from topcoder-platform/slack_fixes
Slack fixes
2 parents 30c5200 + cc5ddeb commit 0b8e801

File tree

7 files changed

+184
-21
lines changed

7 files changed

+184
-21
lines changed

src/common/constants.js

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@
1010
const _ = require('lodash');
1111
const config = require('config');
1212

13+
const projectTypes = {
14+
app_dev: 'Full App',
15+
generic: 'Work Project',
16+
visual_prototype: 'Design & Prototype',
17+
visual_design: 'Design',
18+
};
19+
const icons = {
20+
slack: {
21+
CoderBotIcon: 'https://emoji.slack-edge.com/T03R80JP7/coder-the-bot/85ae574c0c7063ef.png',
22+
CoderErrorIcon: 'https://emoji.slack-edge.com/T03R80JP7/coder-error/cd2633216e7fd385.png',
23+
CoderGrinningIcon: 'https://emoji.slack-edge.com/T03R80JP7/coder-grinning/a3b7f3fe9e838377.png',
24+
},
25+
};
1326
module.exports = {
1427
// The event types to be consumed from the source RabbitMQ
1528
events: {
@@ -30,7 +43,7 @@ module.exports = {
3043
fallback: 'A project is ready to be reviewed.',
3144
title: _.get(data, 'project.name', ''),
3245
title_link: `https://connect.${config.get('AUTH_DOMAIN')}/projects/${data.project.id}/`,
33-
text: _.truncate(_.get(data, 'project.description', ''), {length: 200, separator: /,? +.,/ }),
46+
text: _.truncate(_.get(data, 'project.description', ''), { length: 200, separator: /,? +.,/ }),
3447
ts: (new Date(_.get(data, 'project.updatedAt', null))).getTime() / 1000,
3548
fields: [
3649
{
@@ -40,24 +53,74 @@ module.exports = {
4053
},
4154
{
4255
title: 'Owner',
43-
value: `${_.get(data, 'owner.firstName', '')} ${_.get(data, 'owner.lastName', '')}` ,
56+
value: `${_.get(data, 'owner.firstName', '')} ${_.get(data, 'owner.lastName', '')}`,
57+
short: false,
58+
},
59+
{
60+
title: 'Project Type',
61+
value: projectTypes[data.project.type],
4462
short: false,
4563
},
4664
],
4765
};
4866
},
4967
projectUnclaimed: (data) => {
5068
return {
69+
icon_url: icons.slack.CoderBotIcon,
5170
channel: `${config.get('SLACK_CHANNEL_COPILOTS')}`,
5271
pretext: 'A project has been reviewed and needs a copilot. Please check it out and claim it.',
5372
fallback: 'A project has been reviewed and needs a copilot. Please check it out and claim it.',
5473
title: _.get(data, 'project.name', ''),
5574
title_link: `https://connect.${config.get('AUTH_DOMAIN')}/projects/${data.project.id}/`,
56-
text: _.truncate(_.get(data, 'project.description', ''), {length: 200, separator: /,? +.,/ }),
75+
text: _.truncate(_.get(data, 'project.description', ''), { length: 200, separator: /,? +.,/ }),
76+
ts: (new Date(_.get(data, 'project.updatedAt', null))).getTime() / 1000,
77+
fields: [
78+
{
79+
title: 'Project Type',
80+
value: projectTypes[data.project.type],
81+
short: false,
82+
},
83+
]
84+
}
85+
},
86+
projectUnclaimedReposted: (data) => {
87+
return {
88+
icon_url: icons.slack.CoderErrorIcon,
89+
channel: `${config.get('SLACK_CHANNEL_COPILOTS')}`,
90+
pretext: 'We\'re still looking for a copilot for a reviewed project. Please check it out and claim it.',
91+
fallback: 'We\'re still looking for a copilot for a reviewed project. Please check it out and claim it.',
92+
title: _.get(data, 'project.name', ''),
93+
title_link: `https://connect.${config.get('AUTH_DOMAIN')}/projects/${data.project.id}/`,
94+
text: _.truncate(_.get(data, 'project.description', ''), { length: 200, separator: /,? +.,/ }),
5795
ts: (new Date(_.get(data, 'project.updatedAt', null))).getTime() / 1000,
58-
fields: []
96+
fields: [
97+
{
98+
title: 'Project Type',
99+
value: projectTypes[data.project.type],
100+
short: false,
101+
},
102+
]
59103
}
60-
}
104+
},
105+
projectClaimed: (data) => {
106+
return {
107+
icon_url: icons.slack.CoderGrinningIcon,
108+
channel: `${config.get('SLACK_CHANNEL_COPILOTS')}`,
109+
pretext: `${data.firstName} ${data.lastName} has claimed a project. Welcome to the team!`,
110+
fallback: `${data.firstName} ${data.lastName} has claimed a project. Welcome to the team!`,
111+
title: _.get(data, 'project.name', ''),
112+
title_link: `https://connect.${config.get('AUTH_DOMAIN')}/projects/${data.project.id}/`,
113+
text: _.truncate(_.get(data, 'project.description', ''), { length: 200, separator: /,? +.,/ }),
114+
ts: (new Date(_.get(data, 'project.updatedAt', null))).getTime() / 1000,
115+
fields: [
116+
{
117+
title: 'Project Type',
118+
value: projectTypes[data.project.type],
119+
short: false,
120+
},
121+
]
122+
}
123+
},
61124
},
62125
discourse: {
63126
project: {
@@ -107,6 +170,10 @@ module.exports = {
107170
title: 'Your project has a new owner',
108171
content: (data) => `${data.firstName} ${data.lastName} is now responsible for project <a href="${data.projectUrl}" rel="nofollow">${data.projectName}</a>. Good luck ${data.firstName}.`,
109172
},
173+
ownerAdded: {
174+
title: 'Ownership changed',
175+
content: (data) => `Your project has a new owner ${data.firstName} ${data.lastName} is now responsible for project Project title. Good luck ${data.firstName}!`,
176+
},
110177
},
111178
},
112179
project: {
@@ -138,4 +205,6 @@ module.exports = {
138205
customer: 'customer',
139206
copilot: 'copilot',
140207
},
208+
projectTypes,
209+
icons,
141210
};

src/handlers/memberEvents.js

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
const config = require('config');
1111
const constants = require('../common/constants');
1212
const util = require('./util');
13+
const _ = require('lodash');
1314

1415
/**
1516
* Create notifications from project.member.added events
@@ -23,12 +24,32 @@ function* memberAdded(logger, data) {
2324
];
2425

2526
let topic;
26-
if (data.role === constants.memberRoles.customer) {
27+
const notifications = {
28+
slack: {
29+
copilot: [],
30+
},
31+
};
32+
if (data.role === constants.memberRoles.customer && data.isPrimary) {
33+
topic = constants.notifications.discourse.teamMembers.ownerAdded;
34+
} else if (data.role === constants.memberRoles.customer) {
2735
topic = constants.notifications.discourse.teamMembers.added;
2836
} else if (data.role === constants.memberRoles.manager) {
2937
topic = constants.notifications.discourse.teamMembers.managerJoined;
3038
} else if (data.role === constants.memberRoles.copilot) {
3139
topic = constants.notifications.discourse.teamMembers.copilotJoined;
40+
// Notify project claimed
41+
if ((project.status === constants.projectStatuses.active ||
42+
project.status === constants.projectStatuses.reviewed)
43+
&& _.filter(project.members, ['role', 'copilot']).length < 2) {
44+
const slackNotification = util.buildSlackNotification(
45+
{
46+
project,
47+
firstName: addedMember.firstName,
48+
lastName: addedMember.lastName,
49+
},
50+
constants.notifications.slack.projectClaimed);
51+
notifications.slack.copilot.push(slackNotification);
52+
}
3253
}
3354

3455
const topicData = {
@@ -38,13 +59,11 @@ function* memberAdded(logger, data) {
3859
lastName: addedMember.lastName,
3960
};
4061

41-
const notifications = {
42-
discourse: [{
43-
projectId: project.id,
44-
title: topic.title,
45-
content: topic.content(topicData),
46-
}],
47-
};
62+
notifications.discourse = [{
63+
projectId: project.id,
64+
title: topic.title,
65+
content: topic.content(topicData),
66+
}];
4867
return notifications;
4968
}
5069

src/handlers/projectEvents.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ function* projectUnclaimedNotifications(logger, data) {
129129
projectCopilotIds.length === 0) {
130130
notifications.delayed = data;
131131
const slackNotification = util.buildSlackNotification(
132-
{ project, },
133-
constants.notifications.slack.projectUnclaimed
134-
)
132+
{ project },
133+
constants.notifications.slack.projectUnclaimedReposted
134+
);
135135
notifications.slack.copilot.push(slackNotification);
136136
}
137137
return notifications;

src/handlers/util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ function buildSlackNotification(data, slackDataGenerator) {
200200
const slackData = slackDataGenerator(data);
201201
return {
202202
username: config.get('SLACK_USERNAME'),
203-
icon_url: config.get('SLACK_ICON_URL'),
203+
icon_url: slackData.url || config.get('SLACK_ICON_URL'),
204204
channel: slackData.channel,
205205
attachments: [{
206206
color: "#36a64f",

src/test/app.test.js

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const sampleEvents = {
2727
updatedReviewedAnotherStatus: require('./data/events.updated.reviewed.anotherStatus.json'),
2828
updatedReviewedSameStatus: require('./data/events.updated.reviewed.sameStatus.json'),
2929
memberAddedTeamMember: require('./data/events.memberAdded.teamMember.json'),
30+
memberAddedOwner: require('./data/events.memberAdded.owner.json'),
3031
memberAddedManager: require('./data/events.memberAdded.manager.json'),
3132
memberAddedCopilot: require('./data/events.memberAdded.copilot.json'),
3233
memberRemovedLeft: require('./data/events.memberRemoved.left.json'),
@@ -55,7 +56,13 @@ const expectedSlackNotficationBase = {
5556
title: "test",
5657
title_link: "https://connect.topcoder-dev.com/projects/1/",
5758
text: "test",
58-
fields: [],
59+
fields: [
60+
{
61+
short: false,
62+
title: 'Project Type',
63+
value: 'Design',
64+
},
65+
],
5966
footer: "Topcoder",
6067
footer_icon: "https://emoji.slack-edge.com/T03R80JP7/topcoder/7c68acd90a6b6d77.png",
6168
ts: 1478304000,
@@ -66,7 +73,21 @@ const expectedSlackCopilotNotification = _.cloneDeep(expectedSlackNotficationBas
6673
_.extend(expectedSlackCopilotNotification.attachments[0], {
6774
pretext: 'A project has been reviewed and needs a copilot. Please check it out and claim it.',
6875
fallback: 'A project has been reviewed and needs a copilot. Please check it out and claim it.',
69-
})
76+
});
77+
78+
const expectedRepostedSlackCopilotNotification = _.cloneDeep(expectedSlackNotficationBase);
79+
_.extend(expectedRepostedSlackCopilotNotification.attachments[0], {
80+
pretext: 'We\'re still looking for a copilot for a reviewed project. Please check it out and claim it.',
81+
fallback: 'We\'re still looking for a copilot for a reviewed project. Please check it out and claim it.',
82+
});
83+
const expectedClaimedSlackCopilotNotification = _.cloneDeep(expectedSlackNotficationBase);
84+
_.extend(expectedClaimedSlackCopilotNotification.attachments[0], {
85+
pretext: 'F_user L_user has claimed a project. Welcome to the team!',
86+
fallback: 'F_user L_user has claimed a project. Welcome to the team!',
87+
text: 'Project description 1',
88+
title: 'Project name 1',
89+
ts: '1477671612',
90+
});
7091

7192
const expectedManagerSlackNotification = _.cloneDeep(expectedSlackNotficationBase);
7293
_.extend(expectedManagerSlackNotification.attachments[0], {
@@ -75,6 +96,11 @@ _.extend(expectedManagerSlackNotification.attachments[0], {
7596
fields: [
7697
{ title: 'Ref Code', value: '', short: false },
7798
{ title: 'Owner', value: 'F_user L_user', short: false },
99+
{
100+
short: false,
101+
title: 'Project Type',
102+
value: 'Design',
103+
},
78104
]
79105
})
80106

@@ -278,7 +304,7 @@ describe('app', () => {
278304
assertCount += 1;
279305
sinon.assert.notCalled(spy);
280306
const params = slackSpy.lastCall.args;
281-
assert.deepEqual(params[1], expectedSlackCopilotNotification);
307+
assert.deepEqual(params[1], expectedRepostedSlackCopilotNotification);
282308
// console.log('assert#', assertCount)
283309
// console.log('callbackCount#', callbackCount)
284310
// checkAssert(assertCount, callbackCount, done);
@@ -316,6 +342,18 @@ describe('app', () => {
316342
});
317343

318344
describe('`project.member.added` event', () => {
345+
it('should create `Project.Member.ownerAdded` notification', (done) => {
346+
sendTestEvent(sampleEvents.memberAddedOwner, 'project.member.added');
347+
setTimeout(() => {
348+
const expectedTitle = 'Ownership changed';
349+
const expectedBody = 'Your project has a new owner F_user L_user is now responsible for project Project title. Good luck F_user!';
350+
const params = spy.lastCall.args;
351+
assert.equal(params[2], expectedTitle);
352+
assert.equal(params[3], expectedBody);
353+
done();
354+
}, testTimeout);
355+
});
356+
319357
it('should create `Project.Member.TeamMemberAdded` notification', (done) => {
320358
sendTestEvent(sampleEvents.memberAddedTeamMember, 'project.member.added');
321359
setTimeout(() => {
@@ -351,6 +389,32 @@ describe('app', () => {
351389
done();
352390
}, testTimeout);
353391
});
392+
it('should create `Project.Member.CopilotJoined` notification and slack copilot joined notification', (done) => {
393+
request.get.restore();
394+
stub = sinon.stub(request, 'get');
395+
stub.withArgs(sinon.match.has('url', `${config.API_BASE_URL}/v4/projects/1`))
396+
.yields(null, { statusCode: 200 }, sampleProjects.projectTest);
397+
stub.withArgs(sinon.match.has('url', `${config.API_BASE_URL}/v3/members/_search/?query=userId:40051331`))
398+
.yields(null, { statusCode: 200 }, sampleUsers.user1);
399+
400+
sendTestEvent(sampleEvents.memberAddedCopilot, 'project.member.added');
401+
setTimeout(() => {
402+
const expectedTitle = 'A Topcoder copilot has joined your project';
403+
const expectedBody = 'F_user L_user has joined your project <a href="https://connect.topcoder-dev.com/projects/1/" rel="nofollow">test</a> as a copilot.';
404+
const params = spy.lastCall.args;
405+
assert.equal(params[2], expectedTitle);
406+
assert.equal(params[3], expectedBody);
407+
const slackParams = slackSpy.lastCall.args;
408+
const expectedTestCopilotNotificaton = _.cloneDeep(expectedClaimedSlackCopilotNotification);
409+
_.extend(expectedTestCopilotNotificaton.attachments[0], {
410+
text: 'test',
411+
title: 'test',
412+
ts: 1478304000,
413+
});
414+
assert.deepEqual(slackParams[1], expectedTestCopilotNotificaton);
415+
done();
416+
}, testTimeout);
417+
});
354418
});
355419

356420
describe('`project.member.removed` event', () => {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"id": 1185,
3+
"userId": 40051331,
4+
"role": "customer",
5+
"isPrimary": true,
6+
"createdAt": "2016-11-04T03:57:57.000Z",
7+
"updatedAt": "2016-11-04T03:57:57.000Z",
8+
"createdBy": 40152856,
9+
"updatedBy": 40152856,
10+
"projectId": 1
11+
}

src/test/data/events.memberAdded.teamMember.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"id": 1185,
33
"userId": 40051331,
44
"role": "customer",
5-
"isPrimary": true,
5+
"isPrimary": false,
66
"createdAt": "2016-11-04T03:57:57.000Z",
77
"updatedAt": "2016-11-04T03:57:57.000Z",
88
"createdBy": 40152856,

0 commit comments

Comments
 (0)