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

Slack fixes #66

Merged
merged 7 commits into from
Feb 8, 2017
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
79 changes: 74 additions & 5 deletions src/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@
const _ = require('lodash');
const config = require('config');

const projectTypes = {
app_dev: 'Full App',
generic: 'Work Project',
visual_prototype: 'Design & Prototype',
visual_design: 'Design',
};
const icons = {
slack: {
CoderBotIcon: 'https://emoji.slack-edge.com/T03R80JP7/coder-the-bot/85ae574c0c7063ef.png',
CoderErrorIcon: 'https://emoji.slack-edge.com/T03R80JP7/coder-error/cd2633216e7fd385.png',
CoderGrinningIcon: 'https://emoji.slack-edge.com/T03R80JP7/coder-grinning/a3b7f3fe9e838377.png',
},
};
module.exports = {
// The event types to be consumed from the source RabbitMQ
events: {
Expand All @@ -30,7 +43,7 @@ module.exports = {
fallback: 'A project is ready to be reviewed.',
title: _.get(data, 'project.name', ''),
title_link: `https://connect.${config.get('AUTH_DOMAIN')}/projects/${data.project.id}/`,
text: _.truncate(_.get(data, 'project.description', ''), {length: 200, separator: /,? +.,/ }),
text: _.truncate(_.get(data, 'project.description', ''), { length: 200, separator: /,? +.,/ }),
ts: (new Date(_.get(data, 'project.updatedAt', null))).getTime() / 1000,
fields: [
{
Expand All @@ -40,24 +53,74 @@ module.exports = {
},
{
title: 'Owner',
value: `${_.get(data, 'owner.firstName', '')} ${_.get(data, 'owner.lastName', '')}` ,
value: `${_.get(data, 'owner.firstName', '')} ${_.get(data, 'owner.lastName', '')}`,
short: false,
},
{
title: 'Project Type',
value: projectTypes[data.project.type],
short: false,
},
],
};
},
projectUnclaimed: (data) => {
return {
icon_url: icons.slack.CoderBotIcon,
channel: `${config.get('SLACK_CHANNEL_COPILOTS')}`,
pretext: 'A project has been reviewed and needs a copilot. Please check it out and claim it.',
fallback: 'A project has been reviewed and needs a copilot. Please check it out and claim it.',
title: _.get(data, 'project.name', ''),
title_link: `https://connect.${config.get('AUTH_DOMAIN')}/projects/${data.project.id}/`,
text: _.truncate(_.get(data, 'project.description', ''), {length: 200, separator: /,? +.,/ }),
text: _.truncate(_.get(data, 'project.description', ''), { length: 200, separator: /,? +.,/ }),
ts: (new Date(_.get(data, 'project.updatedAt', null))).getTime() / 1000,
fields: [
{
title: 'Project Type',
value: projectTypes[data.project.type],
short: false,
},
]
}
},
projectUnclaimedReposted: (data) => {
return {
icon_url: icons.slack.CoderErrorIcon,
channel: `${config.get('SLACK_CHANNEL_COPILOTS')}`,
pretext: 'We\'re still looking for a copilot for a reviewed project. Please check it out and claim it.',
fallback: 'We\'re still looking for a copilot for a reviewed project. Please check it out and claim it.',
title: _.get(data, 'project.name', ''),
title_link: `https://connect.${config.get('AUTH_DOMAIN')}/projects/${data.project.id}/`,
text: _.truncate(_.get(data, 'project.description', ''), { length: 200, separator: /,? +.,/ }),
ts: (new Date(_.get(data, 'project.updatedAt', null))).getTime() / 1000,
fields: []
fields: [
{
title: 'Project Type',
value: projectTypes[data.project.type],
short: false,
},
]
}
}
},
projectClaimed: (data) => {
return {
icon_url: icons.slack.CoderGrinningIcon,
channel: `${config.get('SLACK_CHANNEL_COPILOTS')}`,
pretext: `${data.firstName} ${data.lastName} has claimed a project. Welcome to the team!`,
fallback: `${data.firstName} ${data.lastName} has claimed a project. Welcome to the team!`,
title: _.get(data, 'project.name', ''),
title_link: `https://connect.${config.get('AUTH_DOMAIN')}/projects/${data.project.id}/`,
text: _.truncate(_.get(data, 'project.description', ''), { length: 200, separator: /,? +.,/ }),
ts: (new Date(_.get(data, 'project.updatedAt', null))).getTime() / 1000,
fields: [
{
title: 'Project Type',
value: projectTypes[data.project.type],
short: false,
},
]
}
},
},
discourse: {
project: {
Expand Down Expand Up @@ -107,6 +170,10 @@ module.exports = {
title: 'Your project has a new owner',
content: (data) => `${data.firstName} ${data.lastName} is now responsible for project <a href="${data.projectUrl}" rel="nofollow">${data.projectName}</a>. Good luck ${data.firstName}.`,
},
ownerAdded: {
title: 'Ownership changed',
content: (data) => `Your project has a new owner ${data.firstName} ${data.lastName} is now responsible for project Project title. Good luck ${data.firstName}!`,
},
},
},
project: {
Expand Down Expand Up @@ -138,4 +205,6 @@ module.exports = {
customer: 'customer',
copilot: 'copilot',
},
projectTypes,
icons,
};
35 changes: 27 additions & 8 deletions src/handlers/memberEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
const config = require('config');
const constants = require('../common/constants');
const util = require('./util');
const _ = require('lodash');

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

let topic;
if (data.role === constants.memberRoles.customer) {
const notifications = {
slack: {
copilot: [],
},
};
if (data.role === constants.memberRoles.customer && data.isPrimary) {
topic = constants.notifications.discourse.teamMembers.ownerAdded;
} else if (data.role === constants.memberRoles.customer) {
topic = constants.notifications.discourse.teamMembers.added;
} else if (data.role === constants.memberRoles.manager) {
topic = constants.notifications.discourse.teamMembers.managerJoined;
} else if (data.role === constants.memberRoles.copilot) {
topic = constants.notifications.discourse.teamMembers.copilotJoined;
// Notify project claimed
if ((project.status === constants.projectStatuses.active ||
project.status === constants.projectStatuses.reviewed)
&& _.filter(project.members, ['role', 'copilot']).length < 2) {
const slackNotification = util.buildSlackNotification(
{
project,
firstName: addedMember.firstName,
lastName: addedMember.lastName,
},
constants.notifications.slack.projectClaimed);
notifications.slack.copilot.push(slackNotification);
}
}

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

const notifications = {
discourse: [{
projectId: project.id,
title: topic.title,
content: topic.content(topicData),
}],
};
notifications.discourse = [{
projectId: project.id,
title: topic.title,
content: topic.content(topicData),
}];
return notifications;
}

Expand Down
6 changes: 3 additions & 3 deletions src/handlers/projectEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ function* projectUnclaimedNotifications(logger, data) {
projectCopilotIds.length === 0) {
notifications.delayed = data;
const slackNotification = util.buildSlackNotification(
{ project, },
constants.notifications.slack.projectUnclaimed
)
{ project },
constants.notifications.slack.projectUnclaimedReposted
);
notifications.slack.copilot.push(slackNotification);
}
return notifications;
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ function buildSlackNotification(data, slackDataGenerator) {
const slackData = slackDataGenerator(data);
return {
username: config.get('SLACK_USERNAME'),
icon_url: config.get('SLACK_ICON_URL'),
icon_url: slackData.url || config.get('SLACK_ICON_URL'),
channel: slackData.channel,
attachments: [{
color: "#36a64f",
Expand Down
70 changes: 67 additions & 3 deletions src/test/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const sampleEvents = {
updatedReviewedAnotherStatus: require('./data/events.updated.reviewed.anotherStatus.json'),
updatedReviewedSameStatus: require('./data/events.updated.reviewed.sameStatus.json'),
memberAddedTeamMember: require('./data/events.memberAdded.teamMember.json'),
memberAddedOwner: require('./data/events.memberAdded.owner.json'),
memberAddedManager: require('./data/events.memberAdded.manager.json'),
memberAddedCopilot: require('./data/events.memberAdded.copilot.json'),
memberRemovedLeft: require('./data/events.memberRemoved.left.json'),
Expand Down Expand Up @@ -55,7 +56,13 @@ const expectedSlackNotficationBase = {
title: "test",
title_link: "https://connect.topcoder-dev.com/projects/1/",
text: "test",
fields: [],
fields: [
{
short: false,
title: 'Project Type',
value: 'Design',
},
],
footer: "Topcoder",
footer_icon: "https://emoji.slack-edge.com/T03R80JP7/topcoder/7c68acd90a6b6d77.png",
ts: 1478304000,
Expand All @@ -66,7 +73,21 @@ const expectedSlackCopilotNotification = _.cloneDeep(expectedSlackNotficationBas
_.extend(expectedSlackCopilotNotification.attachments[0], {
pretext: 'A project has been reviewed and needs a copilot. Please check it out and claim it.',
fallback: 'A project has been reviewed and needs a copilot. Please check it out and claim it.',
})
});

const expectedRepostedSlackCopilotNotification = _.cloneDeep(expectedSlackNotficationBase);
_.extend(expectedRepostedSlackCopilotNotification.attachments[0], {
pretext: 'We\'re still looking for a copilot for a reviewed project. Please check it out and claim it.',
fallback: 'We\'re still looking for a copilot for a reviewed project. Please check it out and claim it.',
});
const expectedClaimedSlackCopilotNotification = _.cloneDeep(expectedSlackNotficationBase);
_.extend(expectedClaimedSlackCopilotNotification.attachments[0], {
pretext: 'F_user L_user has claimed a project. Welcome to the team!',
fallback: 'F_user L_user has claimed a project. Welcome to the team!',
text: 'Project description 1',
title: 'Project name 1',
ts: '1477671612',
});

const expectedManagerSlackNotification = _.cloneDeep(expectedSlackNotficationBase);
_.extend(expectedManagerSlackNotification.attachments[0], {
Expand All @@ -75,6 +96,11 @@ _.extend(expectedManagerSlackNotification.attachments[0], {
fields: [
{ title: 'Ref Code', value: '', short: false },
{ title: 'Owner', value: 'F_user L_user', short: false },
{
short: false,
title: 'Project Type',
value: 'Design',
},
]
})

Expand Down Expand Up @@ -278,7 +304,7 @@ describe('app', () => {
assertCount += 1;
sinon.assert.notCalled(spy);
const params = slackSpy.lastCall.args;
assert.deepEqual(params[1], expectedSlackCopilotNotification);
assert.deepEqual(params[1], expectedRepostedSlackCopilotNotification);
// console.log('assert#', assertCount)
// console.log('callbackCount#', callbackCount)
// checkAssert(assertCount, callbackCount, done);
Expand Down Expand Up @@ -316,6 +342,18 @@ describe('app', () => {
});

describe('`project.member.added` event', () => {
it('should create `Project.Member.ownerAdded` notification', (done) => {
sendTestEvent(sampleEvents.memberAddedOwner, 'project.member.added');
setTimeout(() => {
const expectedTitle = 'Ownership changed';
const expectedBody = 'Your project has a new owner F_user L_user is now responsible for project Project title. Good luck F_user!';
const params = spy.lastCall.args;
assert.equal(params[2], expectedTitle);
assert.equal(params[3], expectedBody);
done();
}, testTimeout);
});

it('should create `Project.Member.TeamMemberAdded` notification', (done) => {
sendTestEvent(sampleEvents.memberAddedTeamMember, 'project.member.added');
setTimeout(() => {
Expand Down Expand Up @@ -351,6 +389,32 @@ describe('app', () => {
done();
}, testTimeout);
});
it('should create `Project.Member.CopilotJoined` notification and slack copilot joined notification', (done) => {
request.get.restore();
stub = sinon.stub(request, 'get');
stub.withArgs(sinon.match.has('url', `${config.API_BASE_URL}/v4/projects/1`))
.yields(null, { statusCode: 200 }, sampleProjects.projectTest);
stub.withArgs(sinon.match.has('url', `${config.API_BASE_URL}/v3/members/_search/?query=userId:40051331`))
.yields(null, { statusCode: 200 }, sampleUsers.user1);

sendTestEvent(sampleEvents.memberAddedCopilot, 'project.member.added');
setTimeout(() => {
const expectedTitle = 'A Topcoder copilot has joined your project';
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.';
const params = spy.lastCall.args;
assert.equal(params[2], expectedTitle);
assert.equal(params[3], expectedBody);
const slackParams = slackSpy.lastCall.args;
const expectedTestCopilotNotificaton = _.cloneDeep(expectedClaimedSlackCopilotNotification);
_.extend(expectedTestCopilotNotificaton.attachments[0], {
text: 'test',
title: 'test',
ts: 1478304000,
});
assert.deepEqual(slackParams[1], expectedTestCopilotNotificaton);
done();
}, testTimeout);
});
});

describe('`project.member.removed` event', () => {
Expand Down
11 changes: 11 additions & 0 deletions src/test/data/events.memberAdded.owner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": 1185,
"userId": 40051331,
"role": "customer",
"isPrimary": true,
"createdAt": "2016-11-04T03:57:57.000Z",
"updatedAt": "2016-11-04T03:57:57.000Z",
"createdBy": 40152856,
"updatedBy": 40152856,
"projectId": 1
}
2 changes: 1 addition & 1 deletion src/test/data/events.memberAdded.teamMember.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": 1185,
"userId": 40051331,
"role": "customer",
"isPrimary": true,
"isPrimary": false,
"createdAt": "2016-11-04T03:57:57.000Z",
"updatedAt": "2016-11-04T03:57:57.000Z",
"createdBy": 40152856,
Expand Down