Skip to content

Commit 4a052d9

Browse files
Merge pull request #859 from topcoder-platform/fix-2321
Fix 2321
2 parents ccfcc6e + e84d3c0 commit 4a052d9

File tree

5 files changed

+151
-146
lines changed

5 files changed

+151
-146
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-1612', 'fix-project-exposing']
152+
only: ['develop', 'migration-setup', 'PM-1612', 'fix-2321']
153153
- deployProd:
154154
context : org-global
155155
filters:

config/development.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"copilotPortalUrl": "https://copilots.topcoder-dev.com",
77
"fileServiceEndpoint": "https://api.topcoder-dev.com/v5/files",
88
"memberServiceEndpoint": "https://api.topcoder-dev.com/v5/members",
9-
"identityServiceEndpoint": "https://api.topcoder-dev.com/v3/",
9+
"identityServiceEndpoint": "https://api.topcoder-dev.com/v6/",
1010
"taasJobApiUrl": "https://api.topcoder-dev.com/v5/jobs",
1111
"sfdcBillingAccountNameField": "Billing_Account_name__c",
1212
"sfdcBillingAccountMarkupField": "Mark_Up__c",
Lines changed: 87 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
import _ from 'lodash';
21
import config from 'config';
32
import moment from 'moment';
43
import { Op } from 'sequelize';
54

65
import models from '../../models';
7-
import { CONNECT_NOTIFICATION_EVENT, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, TEMPLATE_IDS, USER_ROLE } from '../../constants';
6+
import {
7+
CONNECT_NOTIFICATION_EVENT,
8+
COPILOT_OPPORTUNITY_STATUS,
9+
COPILOT_REQUEST_STATUS,
10+
TEMPLATE_IDS,
11+
USER_ROLE,
12+
} from '../../constants';
813
import util from '../../util';
914
import { createEvent } from '../../services/busApi';
1015
import { getCopilotTypeLabel } from '../../utils/copilot';
@@ -17,85 +22,86 @@ const resolveTransaction = (transaction, callback) => {
1722
return models.sequelize.transaction(callback);
1823
};
1924

20-
module.exports = (req, data, existingTransaction) => {
25+
module.exports = async (req, data, existingTransaction) => {
2126
const { projectId, copilotRequestId, opportunityTitle, type, startDate } = data;
2227

23-
return resolveTransaction(existingTransaction, transaction =>
24-
models.Project.findOne({
25-
where: { id: projectId, deletedAt: { $eq: null } },
26-
}, { transaction })
27-
.then((existingProject) => {
28-
if (!existingProject) {
29-
const err = new Error(`active project not found for project id ${projectId}`);
30-
err.status = 404;
31-
throw err;
32-
}
33-
return models.CopilotRequest.findByPk(copilotRequestId, { transaction })
34-
.then((existingCopilotRequest) => {
35-
if (!existingCopilotRequest) {
36-
const err = new Error(`no active copilot request found for copilot request id ${copilotRequestId}`);
37-
err.status = 404;
38-
throw err;
39-
}
40-
41-
return existingCopilotRequest.update({
42-
status: COPILOT_REQUEST_STATUS.APPROVED,
43-
}, { transaction }).then(() => models.CopilotOpportunity
44-
.findOne({
45-
where: {
46-
projectId,
47-
type: data.type,
48-
status: {
49-
[Op.in]: [COPILOT_OPPORTUNITY_STATUS.ACTIVE],
50-
}
51-
},
52-
})
53-
.then((existingCopilotOpportunityOfSameType) => {
54-
if (existingCopilotOpportunityOfSameType) {
55-
const err = new Error('There\'s an active opportunity of same type already!');
56-
_.assign(err, {
57-
status: 403,
58-
});
59-
throw err;
60-
}
61-
return models.CopilotOpportunity
62-
.create(data, { transaction });
63-
}))
64-
.then(async (opportunity) => {
65-
const roles = await util.getRolesByRoleName(USER_ROLE.TC_COPILOT, req.log, req.id);
66-
const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id);
67-
const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL;
68-
const copilotPortalUrl = config.get('copilotPortalUrl');
69-
req.log.info("Sending emails to all copilots about new opportunity");
70-
71-
const sendNotification = (userName, recipient) => createEvent(emailEventType, {
72-
data: {
73-
user_name: userName,
74-
opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`,
75-
work_manager_url: config.get('workManagerUrl'),
76-
opportunity_type: getCopilotTypeLabel(type),
77-
opportunity_title: opportunityTitle,
78-
start_date: moment(startDate).format("DD-MM-YYYY"),
79-
},
80-
sendgrid_template_id: TEMPLATE_IDS.CREATE_REQUEST,
81-
recipients: [recipient],
82-
version: 'v3',
83-
}, req.log);
84-
85-
subjects.forEach(subject => sendNotification(subject.handle, subject.email));
86-
87-
// send email to notify via slack
88-
sendNotification('Copilots', config.copilotsSlackEmail);
89-
90-
req.log.info("Finished sending emails to copilots");
91-
92-
return opportunity;
93-
})
94-
.catch((err) => {
95-
transaction.rollback();
96-
return Promise.reject(err);
97-
});
98-
});
99-
}),
100-
);
28+
return resolveTransaction(existingTransaction, async (transaction) => {
29+
try {
30+
const existingProject = await models.Project.findOne({
31+
where: { id: projectId, deletedAt: { $eq: null } },
32+
transaction,
33+
});
34+
35+
if (!existingProject) {
36+
const err = new Error(`active project not found for project id ${projectId}`);
37+
err.status = 404;
38+
throw err;
39+
}
40+
41+
const copilotRequest = await models.CopilotRequest.findByPk(copilotRequestId, { transaction });
42+
43+
if (!copilotRequest) {
44+
const err = new Error(`no active copilot request found for copilot request id ${copilotRequestId}`);
45+
err.status = 404;
46+
throw err;
47+
}
48+
49+
await copilotRequest.update({ status: COPILOT_REQUEST_STATUS.APPROVED }, { transaction });
50+
51+
const existingOpportunity = await models.CopilotOpportunity.findOne({
52+
where: {
53+
projectId,
54+
type: data.type,
55+
status: { [Op.in]: [COPILOT_OPPORTUNITY_STATUS.ACTIVE] },
56+
},
57+
transaction,
58+
});
59+
60+
if (existingOpportunity) {
61+
const err = new Error('There\'s an active opportunity of same type already!');
62+
err.status = 403;
63+
throw err;
64+
}
65+
66+
const opportunity = await models.CopilotOpportunity.create(data, { transaction });
67+
req.log.debug('Created new copilot opportunity', { opportunityId: opportunity.id });
68+
69+
// Send notifications
70+
try {
71+
const roles = await util.getRolesByRoleName(USER_ROLE.TC_COPILOT, req.log, req.id);
72+
73+
const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id);
74+
const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL;
75+
const copilotPortalUrl = config.get('copilotPortalUrl');
76+
req.log.info('Sending emails to all copilots about new opportunity');
77+
78+
const sendNotification = (userName, recipient) => createEvent(emailEventType, {
79+
data: {
80+
user_name: userName,
81+
opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`,
82+
work_manager_url: config.get('workManagerUrl'),
83+
opportunity_type: getCopilotTypeLabel(type),
84+
opportunity_title: opportunityTitle,
85+
start_date: moment(startDate).format('DD-MM-YYYY'),
86+
},
87+
sendgrid_template_id: TEMPLATE_IDS.CREATE_REQUEST,
88+
recipients: [recipient],
89+
version: 'v3',
90+
}, req.log);
91+
92+
subjects.forEach(subject => sendNotification(subject.handle, subject.email));
93+
94+
// send email to notify via slack
95+
sendNotification('Copilots', config.copilotsSlackEmail);
96+
req.log.info('Finished sending emails to copilots');
97+
} catch (emailErr) {
98+
req.log.error('Error sending notifications', { error: emailErr });
99+
}
100+
101+
return opportunity;
102+
} catch (err) {
103+
req.log.error('approveRequest failed', { error: err });
104+
throw err; // let outer transaction handle rollback
105+
}
106+
});
101107
};

src/routes/copilotRequest/create.js

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const addCopilotRequestValidations = {
3939

4040
module.exports = [
4141
validate(addCopilotRequestValidations),
42-
(req, res, next) => {
42+
async (req, res, next) => {
4343
const data = req.body;
4444
if (!util.hasPermissionByReq(PERMISSION.MANAGE_COPILOT_REQUEST, req)) {
4545
const err = new Error('Unable to create copilot request');
@@ -58,66 +58,64 @@ module.exports = [
5858
updatedBy: req.authUser.userId,
5959
});
6060

61-
return models.sequelize.transaction((transaction) => {
62-
req.log.debug('Create Copilot request transaction', data);
63-
return models.Project.findOne({
64-
where: { id: projectId, deletedAt: { $eq: null } },
65-
})
66-
.then((existingProject) => {
67-
if (!existingProject) {
68-
const err = new Error(`active project not found for project id ${projectId}`);
69-
err.status = 404;
70-
throw err;
71-
}
72-
return models.CopilotRequest.findOne({
73-
where: {
74-
createdBy: req.authUser.userId,
75-
projectId,
76-
status: {
77-
[Op.in]: [COPILOT_REQUEST_STATUS.NEW, COPILOT_REQUEST_STATUS.APPROVED, COPILOT_REQUEST_STATUS.SEEKING],
78-
},
79-
},
80-
}).then((copilotRequest) => {
81-
if (copilotRequest && copilotRequest.data.projectType === data.data.projectType) {
82-
const err = new Error('There\'s a request of same type already!');
83-
_.assign(err, {
84-
status: 400,
85-
});
86-
throw err;
87-
}
61+
try {
62+
const copilotRequest = await models.sequelize.transaction(async (transaction) => {
63+
req.log.debug('Create copilot request transaction', { data });
64+
65+
const existingProject = await models.Project.findOne({
66+
where: { id: projectId, deletedAt: { $eq: null } },
67+
transaction,
68+
});
8869

89-
return models.CopilotRequest
90-
.create(data, { transaction });
91-
}).then((copilotRequest) => {
92-
/**
93-
* Automatically approve the copilot request.
94-
*/
95-
const approveData = _.assign({
96-
projectId,
97-
copilotRequestId: copilotRequest.id,
98-
createdBy: req.authUser.userId,
99-
updatedBy: req.authUser.userId,
100-
type: copilotRequest.data.projectType,
101-
opportunityTitle: copilotRequest.data.opportunityTitle,
102-
startDate: copilotRequest.data.startDate,
103-
});
104-
return approveRequest(req, approveData, transaction).then(() => copilotRequest);
105-
}).then(copilotRequest => res.status(201).json(copilotRequest))
106-
.catch((err) => {
107-
try {
108-
transaction.rollback();
109-
} catch (e) {
110-
_.noop(e);
111-
}
112-
return Promise.reject(err);
113-
});
70+
if (!existingProject) {
71+
const err = new Error(`Active project not found for project id ${projectId}`);
72+
err.status = 404;
73+
throw err;
74+
}
75+
76+
const existingRequest = await models.CopilotRequest.findOne({
77+
where: {
78+
createdBy: req.authUser.userId,
79+
projectId,
80+
status: {
81+
[Op.in]: [
82+
COPILOT_REQUEST_STATUS.NEW,
83+
COPILOT_REQUEST_STATUS.APPROVED,
84+
COPILOT_REQUEST_STATUS.SEEKING,
85+
],
86+
},
87+
},
88+
transaction,
11489
});
115-
})
116-
.catch((err) => {
117-
if (err.message) {
118-
_.assign(err, { details: err.message });
90+
91+
if (existingRequest && existingRequest.data.projectType === data.data.projectType) {
92+
const err = new Error('There\'s a request of same type already!');
93+
err.status = 400;
94+
throw err;
11995
}
120-
util.handleError('Error creating copilot request', err, req, next);
96+
97+
const newRequest = await models.CopilotRequest.create(data, { transaction });
98+
99+
await approveRequest(req, {
100+
projectId,
101+
copilotRequestId: newRequest.id,
102+
createdBy: req.authUser.userId,
103+
updatedBy: req.authUser.userId,
104+
type: newRequest.data.projectType,
105+
opportunityTitle: newRequest.data.opportunityTitle,
106+
startDate: newRequest.data.startDate,
107+
}, transaction);
108+
109+
return newRequest;
121110
});
111+
112+
return res.status(201).json(copilotRequest);
113+
} catch (err) {
114+
req.log.error('Error creating copilot request', { error: err });
115+
if (err.message) _.assign(err, { details: err.message });
116+
util.handleError('Error creating copilot request', err, req, next);
117+
return undefined;
118+
}
122119
},
123120
];
121+

src/util.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -820,10 +820,10 @@ const projectServiceUtils = {
820820
const token = yield this.getM2MToken();
821821
const httpClient = this.getHttpClient({ id: requestId, log: logger });
822822
httpClient.defaults.timeout = 6000;
823-
logger.debug(`${config.identityServiceEndpoint}roles/${roleId}`, "fetching role info");
823+
logger.debug(`${config.identityServiceEndpoint}roles/${roleId}`, 'fetching role info');
824824
return httpClient.get(`${config.identityServiceEndpoint}roles/${roleId}`, {
825825
params: {
826-
fields: `subjects`,
826+
fields: 'subjects',
827827
},
828828
headers: {
829829
'Content-Type': 'application/json',
@@ -834,7 +834,7 @@ const projectServiceUtils = {
834834
return _.get(res, 'data.result.content', []);
835835
});
836836
} catch (err) {
837-
logger.debug(err, "error on getting role info");
837+
logger.debug(err, 'error on getting role info');
838838
return Promise.reject(err);
839839
}
840840
}),
@@ -853,8 +853,9 @@ const projectServiceUtils = {
853853
Authorization: `Bearer ${token}`,
854854
},
855855
}).then((res) => {
856-
logger.debug(`Roles by ${roleName}: ${JSON.stringify(res.data.result.content)}`);
857-
return _.get(res, 'data.result.content', [])
856+
const roles = res.data;
857+
logger.debug(`Roles by ${roleName}: ${JSON.stringify(roles)}`);
858+
return roles
858859
.filter(item => item.roleName === roleName)
859860
.map(r => r.id);
860861
});

0 commit comments

Comments
 (0)