Skip to content

Commit c958c3c

Browse files
authored
Merge pull request #849 from topcoder-platform/pm-1611
feat(PM-1611): send email when opportunity is completed or canceled
2 parents 82c1908 + d2425b4 commit c958c3c

File tree

7 files changed

+87
-17
lines changed

7 files changed

+87
-17
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ workflows:
149149
context : org-global
150150
filters:
151151
branches:
152-
only: ['develop', 'migration-setup', 'pm-1613']
152+
only: ['develop', 'migration-setup', 'pm-1611']
153153
- deployProd:
154154
context : org-global
155155
filters:

src/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ export const TEMPLATE_IDS = {
314314
INFORM_PM_COPILOT_APPLICATION_ACCEPTED: 'd-b35d073e302b4279a1bd208fcfe96f58',
315315
COPILOT_ALREADY_PART_OF_PROJECT: 'd-003d41cdc9de4bbc9e14538e8f2e0585',
316316
COPILOT_APPLICATION_ACCEPTED: 'd-eef5e7568c644940b250e76d026ced5b',
317+
COPILOT_OPPORTUNITY_COMPLETED: 'd-dc448919d11b4e7d8b4ba351c4b67b8b',
318+
COPILOT_OPPORTUNITY_CANCELED: 'd-2a67ba71e82f4d70891fe6989c3522a3'
317319
}
318320
export const REGEX = {
319321
URL: /^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=;]*)?$/, // eslint-disable-line

src/models/copilotRequest.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import _ from 'lodash';
22
import { COPILOT_REQUEST_STATUS } from '../constants';
33

44
module.exports = function defineCopilotRequest(sequelize, DataTypes) {
5-
const CopliotRequest = sequelize.define('CopilotRequest', {
5+
const CopilotRequest = sequelize.define('CopilotRequest', {
66
id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
77
status: {
88
type: DataTypes.STRING(16),
@@ -30,9 +30,10 @@ module.exports = function defineCopilotRequest(sequelize, DataTypes) {
3030
indexes: [],
3131
});
3232

33-
CopliotRequest.associate = (models) => {
34-
CopliotRequest.hasMany(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'copilotRequestId' });
33+
CopilotRequest.associate = (models) => {
34+
CopilotRequest.hasMany(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'copilotRequestId' });
35+
CopilotRequest.belongsTo(models.Project, { as: 'project', foreignKey: 'projectId' });
3536
};
3637

37-
return CopliotRequest;
38+
return CopilotRequest;
3839
};

src/routes/copilotOpportunity/assign.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,33 @@ module.exports = [
3232
return next(err);
3333
}
3434

35+
const sendEmailToAllApplicants = async (copilotRequest, allApplications) => {
36+
37+
const userIds = allApplications.map(item => item.userId);
38+
39+
const users = await util.getMemberDetailsByUserIds(userIds, req.log, req.id);
40+
41+
users.forEach(async (user) => {
42+
req.log.debug(`Sending email notification to copilots who are not accepted`);
43+
const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL;
44+
const copilotPortalUrl = config.get('copilotPortalUrl');
45+
const requestData = copilotRequest.data;
46+
createEvent(emailEventType, {
47+
data: {
48+
opportunity_details_url: `${copilotPortalUrl}/opportunity`,
49+
opportunity_title: requestData.opportunityTitle,
50+
user_name: user ? user.handle : "",
51+
},
52+
sendgrid_template_id: TEMPLATE_IDS.COPILOT_OPPORTUNITY_COMPLETED,
53+
recipients: [user.email],
54+
version: 'v3',
55+
}, req.log);
56+
57+
req.log.debug(`Email sent to copilots who are not accepted`);
58+
});
59+
60+
};
61+
3562
return models.sequelize.transaction(async (t) => {
3663
const opportunity = await models.CopilotOpportunity.findOne({
3764
where: { id: copilotOpportunityId },
@@ -238,6 +265,9 @@ module.exports = [
238265
transaction: t,
239266
});
240267

268+
// Send email to all applicants about opportunity completion
269+
await sendEmailToAllApplicants(copilotRequest, otherApplications);
270+
241271
for (const otherApplication of otherApplications) {
242272
await otherApplication.update({
243273
status: COPILOT_APPLICATION_STATUS.CANCELED,

src/routes/copilotOpportunity/delete.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import _ from 'lodash';
22
import { Op } from 'sequelize';
3+
import config from 'config';
34

45
import models from '../../models';
56
import util from '../../util';
6-
import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, EVENT, INVITE_STATUS, RESOURCES } from '../../constants';
7+
import { CONNECT_NOTIFICATION_EVENT, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, EVENT, INVITE_STATUS, RESOURCES, TEMPLATE_IDS } from '../../constants';
8+
import { createEvent } from '../../services/busApi';
79
import { PERMISSION } from '../../permissions/constants';
810

911

@@ -20,6 +22,31 @@ module.exports = [
2022
// default values
2123
const opportunityId = _.parseInt(req.params.id);
2224

25+
const sendEmailToAllApplicants = async (copilotRequest, applications) => {
26+
const userIds = applications.map(item => item.userId);
27+
const users = await util.getMemberDetailsByUserIds(userIds, req.log, req.id);
28+
29+
users.forEach(async (user) => {
30+
req.log.debug(`Sending email notification to copilots who applied`);
31+
const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL;
32+
const copilotPortalUrl = config.get('copilotPortalUrl');
33+
const requestData = copilotRequest.data;
34+
createEvent(emailEventType, {
35+
data: {
36+
opportunity_details_url: `${copilotPortalUrl}/opportunity`,
37+
opportunity_title: requestData.opportunityTitle,
38+
user_name: user ? user.handle : "",
39+
},
40+
sendgrid_template_id: TEMPLATE_IDS.COPILOT_OPPORTUNITY_CANCELED,
41+
recipients: [user.email],
42+
version: 'v3',
43+
}, req.log);
44+
45+
req.log.debug(`Email sent to copilots who applied`);
46+
});
47+
48+
};
49+
2350
return models.sequelize.transaction(async (transaction) => {
2451
req.log.debug('Canceling Copilot opportunity transaction', opportunityId);
2552
const opportunity = await models.CopilotOpportunity.findOne({
@@ -93,6 +120,8 @@ module.exports = [
93120
invite.toJSON());
94121
}
95122

123+
await sendEmailToAllApplicants(copilotRequest, applications)
124+
96125
res.status(200).send({ id: opportunity.id });
97126
})
98127

src/routes/copilotOpportunity/get.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import util from '../../util';
44
module.exports = [
55
(req, res, next) => {
66
const { id } = req.params;
7-
87
if (!id || isNaN(id)) {
98
return util.handleError('Invalid opportunity ID', null, req, next, 400);
109
}

src/routes/copilotRequest/list.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import _ from 'lodash';
2+
import { Op, Sequelize } from 'sequelize';
23

34
import models from '../../models';
45
import util from '../../util';
56
import { PERMISSION } from '../../permissions/constants';
7+
import { DEFAULT_PAGE_SIZE } from '../../constants';
68

79
module.exports = [
810
(req, res, next) => {
@@ -15,6 +17,10 @@ module.exports = [
1517
return next(err);
1618
}
1719

20+
const page = parseInt(req.query.page, 10) || 1;
21+
const pageSize = parseInt(req.query.pageSize, 10) || DEFAULT_PAGE_SIZE;
22+
const offset = (page - 1) * pageSize;
23+
1824
const projectId = _.parseInt(req.params.projectId);
1925

2026
let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt desc';
@@ -29,19 +35,22 @@ module.exports = [
2935

3036
const whereCondition = projectId ? { projectId } : {};
3137

32-
return models.CopilotRequest.findAll({
38+
return models.CopilotRequest.findAndCountAll({
3339
where: whereCondition,
3440
include: [
35-
{
36-
model: models.CopilotOpportunity,
37-
as: 'copilotOpportunity',
38-
},
41+
{ model: models.CopilotOpportunity, as: 'copilotOpportunity', required: false },
42+
{ model: models.Project, as: 'project', required: false },
3943
],
4044
order: [[sortParams[0], sortParams[1]]],
41-
})
42-
.then(copilotRequests => res.json(copilotRequests))
43-
.catch((err) => {
44-
util.handleError('Error fetching copilot requests', err, req, next);
45-
});
45+
limit: pageSize,
46+
offset,
47+
distinct: true,
48+
subQuery: false,
49+
}).then(({rows: copilotRequests, count}) => util.setPaginationHeaders(req, res, {
50+
count: count,
51+
rows: copilotRequests,
52+
page,
53+
pageSize,
54+
}));
4655
},
4756
];

0 commit comments

Comments
 (0)