Skip to content

Commit a8c2751

Browse files
authored
Merge pull request #852 from topcoder-platform/develop
PROD RELEASE - Copilot Portal updates
2 parents 2a6ad37 + 39962a7 commit a8c2751

File tree

7 files changed

+119
-20
lines changed

7 files changed

+119
-20
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_1']
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,34 @@ 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,
49+
work_manager_url: config.get('workManagerUrl'),
50+
opportunity_title: requestData.opportunityTitle,
51+
user_name: user ? user.handle : "",
52+
},
53+
sendgrid_template_id: TEMPLATE_IDS.COPILOT_OPPORTUNITY_COMPLETED,
54+
recipients: [user.email],
55+
version: 'v3',
56+
}, req.log);
57+
58+
req.log.debug(`Email sent to copilots who are not accepted`);
59+
});
60+
61+
};
62+
3563
return models.sequelize.transaction(async (t) => {
3664
const opportunity = await models.CopilotOpportunity.findOne({
3765
where: { id: copilotOpportunityId },
@@ -238,6 +266,9 @@ module.exports = [
238266
transaction: t,
239267
});
240268

269+
// Send email to all applicants about opportunity completion
270+
await sendEmailToAllApplicants(copilotRequest, otherApplications);
271+
241272
for (const otherApplication of otherApplications) {
242273
await otherApplication.update({
243274
status: COPILOT_APPLICATION_STATUS.CANCELED,

src/routes/copilotOpportunity/delete.js

Lines changed: 31 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,32 @@ 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,
37+
work_manager_url: config.get('workManagerUrl'),
38+
opportunity_title: requestData.opportunityTitle,
39+
user_name: user ? user.handle : "",
40+
},
41+
sendgrid_template_id: TEMPLATE_IDS.COPILOT_OPPORTUNITY_CANCELED,
42+
recipients: [user.email],
43+
version: 'v3',
44+
}, req.log);
45+
46+
req.log.debug(`Email sent to copilots who applied`);
47+
});
48+
49+
};
50+
2351
return models.sequelize.transaction(async (transaction) => {
2452
req.log.debug('Canceling Copilot opportunity transaction', opportunityId);
2553
const opportunity = await models.CopilotOpportunity.findOne({
@@ -93,6 +121,8 @@ module.exports = [
93121
invite.toJSON());
94122
}
95123

124+
await sendEmailToAllApplicants(copilotRequest, applications)
125+
96126
res.status(200).send({ id: opportunity.id });
97127
})
98128

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: 49 additions & 13 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,33 +17,67 @@ 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';
2127
if (sort.indexOf(' ') === -1) {
2228
sort += ' asc';
2329
}
24-
const sortableProps = ['createdAt asc', 'createdAt desc'];
30+
const sortableProps = [
31+
'createdAt asc',
32+
'createdAt desc',
33+
'projectName asc',
34+
'projectName desc',
35+
'opportunityTitle asc',
36+
'opportunityTitle desc',
37+
'projectType asc',
38+
'projectType desc',
39+
'status asc',
40+
'status desc',
41+
];
2542
if (_.indexOf(sortableProps, sort) < 0) {
2643
return util.handleError('Invalid sort criteria', null, req, next);
2744
}
28-
const sortParams = sort.split(' ');
45+
let sortParams = sort.split(' ');
46+
let order = [[sortParams[0], sortParams[1]]];
47+
const relationBasedSortParams = ['projectName'];
48+
const jsonBasedSortParams = ['opportunityTitle', 'projectType'];
49+
if (relationBasedSortParams.includes(sortParams[0])) {
50+
order = [
51+
[{model: models.Project, as: 'project'}, 'name', sortParams[1]],
52+
['id', 'DESC']
53+
]
54+
}
55+
56+
if (jsonBasedSortParams.includes(sortParams[0])) {
57+
order = [
58+
[models.sequelize.literal(`("CopilotRequest"."data"->>'${sortParams[0]}')`), sortParams[1]],
59+
['id', 'DESC'],
60+
]
61+
}
2962

3063
const whereCondition = projectId ? { projectId } : {};
3164

32-
return models.CopilotRequest.findAll({
65+
return models.CopilotRequest.findAndCountAll({
3366
where: whereCondition,
3467
include: [
35-
{
36-
model: models.CopilotOpportunity,
37-
as: 'copilotOpportunity',
38-
},
68+
{ model: models.CopilotOpportunity, as: 'copilotOpportunity', required: false },
69+
{ model: models.Project, as: 'project', required: false },
3970
],
40-
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-
});
71+
order,
72+
limit: pageSize,
73+
offset,
74+
distinct: true,
75+
subQuery: false,
76+
}).then(({rows: copilotRequests, count}) => util.setPaginationHeaders(req, res, {
77+
count: count,
78+
rows: copilotRequests,
79+
page,
80+
pageSize,
81+
}));
4682
},
4783
];

0 commit comments

Comments
 (0)