diff --git a/.circleci/config.yml b/.circleci/config.yml index 3ae3c51c..3f9d7062 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -149,7 +149,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'migration-setup', 'pm-1613'] + only: ['develop', 'migration-setup', 'pm-1611'] - deployProd: context : org-global filters: diff --git a/src/constants.js b/src/constants.js index b2987e65..0dc8af26 100644 --- a/src/constants.js +++ b/src/constants.js @@ -314,6 +314,8 @@ export const TEMPLATE_IDS = { INFORM_PM_COPILOT_APPLICATION_ACCEPTED: 'd-b35d073e302b4279a1bd208fcfe96f58', COPILOT_ALREADY_PART_OF_PROJECT: 'd-003d41cdc9de4bbc9e14538e8f2e0585', COPILOT_APPLICATION_ACCEPTED: 'd-eef5e7568c644940b250e76d026ced5b', + COPILOT_OPPORTUNITY_COMPLETED: 'd-dc448919d11b4e7d8b4ba351c4b67b8b', + COPILOT_OPPORTUNITY_CANCELED: 'd-2a67ba71e82f4d70891fe6989c3522a3' } export const REGEX = { 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 diff --git a/src/models/copilotRequest.js b/src/models/copilotRequest.js index 7ddb924e..c566073e 100644 --- a/src/models/copilotRequest.js +++ b/src/models/copilotRequest.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import { COPILOT_REQUEST_STATUS } from '../constants'; module.exports = function defineCopilotRequest(sequelize, DataTypes) { - const CopliotRequest = sequelize.define('CopilotRequest', { + const CopilotRequest = sequelize.define('CopilotRequest', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, status: { type: DataTypes.STRING(16), @@ -30,9 +30,10 @@ module.exports = function defineCopilotRequest(sequelize, DataTypes) { indexes: [], }); - CopliotRequest.associate = (models) => { - CopliotRequest.hasMany(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'copilotRequestId' }); + CopilotRequest.associate = (models) => { + CopilotRequest.hasMany(models.CopilotOpportunity, { as: 'copilotOpportunity', foreignKey: 'copilotRequestId' }); + CopilotRequest.belongsTo(models.Project, { as: 'project', foreignKey: 'projectId' }); }; - return CopliotRequest; + return CopilotRequest; }; diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index bf021bbb..0756491a 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -32,6 +32,33 @@ module.exports = [ return next(err); } + const sendEmailToAllApplicants = async (copilotRequest, allApplications) => { + + const userIds = allApplications.map(item => item.userId); + + const users = await util.getMemberDetailsByUserIds(userIds, req.log, req.id); + + users.forEach(async (user) => { + req.log.debug(`Sending email notification to copilots who are not accepted`); + const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; + const copilotPortalUrl = config.get('copilotPortalUrl'); + const requestData = copilotRequest.data; + createEvent(emailEventType, { + data: { + opportunity_details_url: `${copilotPortalUrl}/opportunity`, + opportunity_title: requestData.opportunityTitle, + user_name: user ? user.handle : "", + }, + sendgrid_template_id: TEMPLATE_IDS.COPILOT_OPPORTUNITY_COMPLETED, + recipients: [user.email], + version: 'v3', + }, req.log); + + req.log.debug(`Email sent to copilots who are not accepted`); + }); + + }; + return models.sequelize.transaction(async (t) => { const opportunity = await models.CopilotOpportunity.findOne({ where: { id: copilotOpportunityId }, @@ -238,6 +265,9 @@ module.exports = [ transaction: t, }); + // Send email to all applicants about opportunity completion + await sendEmailToAllApplicants(copilotRequest, otherApplications); + for (const otherApplication of otherApplications) { await otherApplication.update({ status: COPILOT_APPLICATION_STATUS.CANCELED, diff --git a/src/routes/copilotOpportunity/delete.js b/src/routes/copilotOpportunity/delete.js index 5336807f..90ff830c 100644 --- a/src/routes/copilotOpportunity/delete.js +++ b/src/routes/copilotOpportunity/delete.js @@ -1,9 +1,11 @@ import _ from 'lodash'; import { Op } from 'sequelize'; +import config from 'config'; import models from '../../models'; import util from '../../util'; -import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, EVENT, INVITE_STATUS, RESOURCES } from '../../constants'; +import { CONNECT_NOTIFICATION_EVENT, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, EVENT, INVITE_STATUS, RESOURCES, TEMPLATE_IDS } from '../../constants'; +import { createEvent } from '../../services/busApi'; import { PERMISSION } from '../../permissions/constants'; @@ -20,6 +22,31 @@ module.exports = [ // default values const opportunityId = _.parseInt(req.params.id); + const sendEmailToAllApplicants = async (copilotRequest, applications) => { + const userIds = applications.map(item => item.userId); + const users = await util.getMemberDetailsByUserIds(userIds, req.log, req.id); + + users.forEach(async (user) => { + req.log.debug(`Sending email notification to copilots who applied`); + const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; + const copilotPortalUrl = config.get('copilotPortalUrl'); + const requestData = copilotRequest.data; + createEvent(emailEventType, { + data: { + opportunity_details_url: `${copilotPortalUrl}/opportunity`, + opportunity_title: requestData.opportunityTitle, + user_name: user ? user.handle : "", + }, + sendgrid_template_id: TEMPLATE_IDS.COPILOT_OPPORTUNITY_CANCELED, + recipients: [user.email], + version: 'v3', + }, req.log); + + req.log.debug(`Email sent to copilots who applied`); + }); + + }; + return models.sequelize.transaction(async (transaction) => { req.log.debug('Canceling Copilot opportunity transaction', opportunityId); const opportunity = await models.CopilotOpportunity.findOne({ @@ -93,6 +120,8 @@ module.exports = [ invite.toJSON()); } + await sendEmailToAllApplicants(copilotRequest, applications) + res.status(200).send({ id: opportunity.id }); }) diff --git a/src/routes/copilotOpportunity/get.js b/src/routes/copilotOpportunity/get.js index 9202a845..e4bad5c8 100644 --- a/src/routes/copilotOpportunity/get.js +++ b/src/routes/copilotOpportunity/get.js @@ -4,7 +4,6 @@ import util from '../../util'; module.exports = [ (req, res, next) => { const { id } = req.params; - if (!id || isNaN(id)) { return util.handleError('Invalid opportunity ID', null, req, next, 400); } diff --git a/src/routes/copilotRequest/list.js b/src/routes/copilotRequest/list.js index a36a3d7b..27664733 100644 --- a/src/routes/copilotRequest/list.js +++ b/src/routes/copilotRequest/list.js @@ -1,8 +1,10 @@ import _ from 'lodash'; +import { Op, Sequelize } from 'sequelize'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; +import { DEFAULT_PAGE_SIZE } from '../../constants'; module.exports = [ (req, res, next) => { @@ -15,6 +17,10 @@ module.exports = [ return next(err); } + const page = parseInt(req.query.page, 10) || 1; + const pageSize = parseInt(req.query.pageSize, 10) || DEFAULT_PAGE_SIZE; + const offset = (page - 1) * pageSize; + const projectId = _.parseInt(req.params.projectId); let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt desc'; @@ -29,19 +35,22 @@ module.exports = [ const whereCondition = projectId ? { projectId } : {}; - return models.CopilotRequest.findAll({ + return models.CopilotRequest.findAndCountAll({ where: whereCondition, include: [ - { - model: models.CopilotOpportunity, - as: 'copilotOpportunity', - }, + { model: models.CopilotOpportunity, as: 'copilotOpportunity', required: false }, + { model: models.Project, as: 'project', required: false }, ], order: [[sortParams[0], sortParams[1]]], - }) - .then(copilotRequests => res.json(copilotRequests)) - .catch((err) => { - util.handleError('Error fetching copilot requests', err, req, next); - }); + limit: pageSize, + offset, + distinct: true, + subQuery: false, + }).then(({rows: copilotRequests, count}) => util.setPaginationHeaders(req, res, { + count: count, + rows: copilotRequests, + page, + pageSize, + })); }, ];