Skip to content

Commit 68522e0

Browse files
authored
Merge pull request #835 from topcoder-platform/pm-1506
feat: allow existing project member to be invited as copilot
2 parents b2ac65f + ce575c7 commit 68522e0

File tree

3 files changed

+117
-8
lines changed

3 files changed

+117
-8
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-1497']
152+
only: ['develop', 'migration-setup', 'pm-1506']
153153
- deployProd:
154154
context : org-global
155155
filters:

src/routes/projectMemberInvites/create.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import _ from 'lodash';
55
import Joi from 'joi';
66
import config from 'config';
77
import { middleware as tcMiddleware } from 'tc-core-library-js';
8+
import { Op } from 'sequelize';
89
import models from '../../models';
910
import util from '../../util';
1011
import {
@@ -299,7 +300,7 @@ module.exports = [
299300

300301
return [];
301302
})
302-
.then((inviteUsers) => {
303+
.then(async (inviteUsers) => {
303304
const members = req.context.currentProjectMembers;
304305
const projectId = _.parseInt(req.params.projectId);
305306
// check user handle exists in returned result
@@ -322,13 +323,39 @@ module.exports = [
322323
const errorMessageForAlreadyMemberUser = 'User with such handle is already a member of the team.';
323324

324325
if (inviteUserIds) {
325-
// remove members already in the team
326+
const existingMembers = _.filter(members, (m) => {
327+
return inviteUserIds.includes(m.userId);
328+
});
329+
330+
req.log.debug(`Existing members: ${JSON.stringify(existingMembers)}`);
331+
332+
const projectMembers = await models.ProjectMember.findAll({
333+
where: {
334+
userId: {
335+
[Op.in]: existingMembers.map(item => item.userId),
336+
},
337+
projectId,
338+
}
339+
});
340+
341+
req.log.debug(`Existing Project Members: ${JSON.stringify(projectMembers)}`);
342+
343+
const existingProjectMembersMap = projectMembers.reduce((acc, current) => {
344+
return Object.assign({}, acc, {
345+
[current.userId]: current,
346+
});
347+
}, {});
348+
349+
req.log.debug(`Existing Project Members Map: ${JSON.stringify(existingProjectMembersMap)}`);
350+
326351
_.remove(inviteUserIds, u => _.some(members, (m) => {
327352
const isPresent = m.userId === u;
328353
if (isPresent) {
329354
failed.push(_.assign({}, {
330355
handle: getUserHandleById(m.userId, inviteUsers),
331356
message: errorMessageForAlreadyMemberUser,
357+
error: "ALREADY_MEMBER",
358+
role: existingProjectMembersMap[m.userId].role,
332359
}));
333360
}
334361
return isPresent;

src/routes/projectMembers/update.js

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import Joi from 'joi';
55
import { middleware as tcMiddleware } from 'tc-core-library-js';
66
import models from '../../models';
77
import util from '../../util';
8-
import { EVENT, RESOURCES, PROJECT_MEMBER_ROLE } from '../../constants';
8+
import { EVENT, RESOURCES, PROJECT_MEMBER_ROLE, COPILOT_REQUEST_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_APPLICATION_STATUS } from '../../constants';
99
import { PERMISSION, PROJECT_TO_TOPCODER_ROLES_MATRIX } from '../../permissions/constants';
10+
import { Op } from 'sequelize';
1011

1112
/**
1213
* API to update a project member.
@@ -27,12 +28,85 @@ const updateProjectMemberValdiations = {
2728
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
2829
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
2930
).required(),
31+
action: Joi.string().optional(),
3032
}),
3133
query: {
3234
fields: Joi.string().optional(),
3335
},
3436
};
3537

38+
const completeAllCopilotRequests = async (req, projectId, _transaction) => {
39+
const allCopilotRequests = await models.CopilotRequest.findAll({
40+
where: {
41+
projectId,
42+
},
43+
transaction: _transaction,
44+
});
45+
46+
req.log.debug(`all copilot requests ${JSON.stringify(allCopilotRequests)}`);
47+
48+
await models.CopilotRequest.update({
49+
status: COPILOT_REQUEST_STATUS.FULFILLED,
50+
}, {
51+
where: {
52+
id: {
53+
[Op.in]: allCopilotRequests.map(item => item.id),
54+
}
55+
},
56+
transaction: _transaction,
57+
});
58+
59+
req.log.debug(`updated all copilot requests`);
60+
61+
const copilotOpportunites = await models.CopilotOpportunity.findAll({
62+
where: {
63+
copilotRequestId: {
64+
[Op.in]: allCopilotRequests.map(item => item.id),
65+
},
66+
},
67+
transaction: _transaction,
68+
});
69+
70+
req.log.debug(`all copilot opportunities ${JSON.stringify(copilotOpportunites)}`);
71+
72+
await models.CopilotOpportunity.update({
73+
status: COPILOT_OPPORTUNITY_STATUS.COMPLETED,
74+
}, {
75+
where: {
76+
id: {
77+
[Op.in]: copilotOpportunites.map(item => item.id),
78+
}
79+
},
80+
transaction: _transaction,
81+
});
82+
83+
req.log.debug(`updated all copilot opportunities`);
84+
85+
const allCopilotApplications = await models.CopilotApplication.findAll({
86+
where: {
87+
opportunityId: {
88+
[Op.in]: copilotOpportunites.map(item => item.id),
89+
},
90+
},
91+
transaction: _transaction,
92+
});
93+
94+
req.log.debug(`all copilot applications ${JSON.stringify(allCopilotApplications)}`);
95+
96+
await models.CopilotApplication.update({
97+
status: COPILOT_APPLICATION_STATUS.CANCELED,
98+
}, {
99+
where: {
100+
id: {
101+
[Op.in]: allCopilotApplications.map(item => item.id),
102+
},
103+
},
104+
transaction: _transaction,
105+
});
106+
107+
req.log.debug(`updated all copilot applications`);
108+
};
109+
36110
module.exports = [
37111
// handles request validations
38112
validate(updateProjectMemberValdiations),
@@ -45,15 +119,16 @@ module.exports = [
45119
let updatedProps = req.body;
46120
const projectId = _.parseInt(req.params.projectId);
47121
const memberRecordId = _.parseInt(req.params.id);
122+
const action = updatedProps.action;
48123
updatedProps = _.pick(updatedProps, ['isPrimary', 'role']);
49124
const fields = req.query.fields ? req.query.fields.split(',') : null;
50125

51126
let previousValue;
52127
// let newValue;
53-
models.sequelize.transaction(() => models.ProjectMember.findOne({
128+
models.sequelize.transaction((_transaction) => models.ProjectMember.findOne({
54129
where: { id: memberRecordId, projectId },
55130
})
56-
.then((_member) => {
131+
.then(async (_member) => {
57132
if (!_member) {
58133
// handle 404
59134
const err = new Error(`project member not found for project id ${projectId} ` +
@@ -76,10 +151,13 @@ module.exports = [
76151
return Promise.reject(err);
77152
}
78153

154+
req.log.debug(`updated props ${JSON.stringify(updatedProps)}`);
155+
req.log.debug(`previous values ${JSON.stringify(previousValue)}`);
79156
// no updates if no change
80-
if (updatedProps.role === previousValue.role &&
157+
if ((updatedProps.role === previousValue.role || action === 'complete-copilot-requests') &&
81158
(_.isUndefined(updatedProps.isPrimary) ||
82159
updatedProps.isPrimary === previousValue.isPrimary)) {
160+
await completeAllCopilotRequests(req, projectId, _transaction);
83161
return Promise.resolve();
84162
}
85163

@@ -121,9 +199,13 @@ module.exports = [
121199
});
122200
})
123201
.then(() => projectMember.reload(projectMember.id))
124-
.then(() => {
202+
.then(async () => {
125203
projectMember = projectMember.get({ plain: true });
126204
projectMember = _.omit(projectMember, ['deletedAt']);
205+
206+
if (['observer', 'customer'].includes(updatedProps.role)) {
207+
await completeAllCopilotRequests(req, projectId, _transaction);
208+
}
127209
})
128210
.then(() => (
129211
util.getObjectsWithMemberDetails([projectMember], fields, req)

0 commit comments

Comments
 (0)