Skip to content

feat(PM-1173): Notify all copilots on copilot opportunity #815

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Jun 10, 2025

Conversation

hentrymartin
Copy link
Collaborator

What's in this PR?

  • Notify all copilots on copilot opportunity

@@ -52,6 +53,12 @@ module.exports = (data, existingTransaction) => {
return models.CopilotOpportunity
.create(data, { transaction });
}))
.then((opportunity) => {
console.log(opportunity);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using console.log for debugging in production code. Consider using a logging library to handle logs appropriately.

.then((opportunity) => {
console.log(opportunity);
const roles = util.getRolesByRoleName('copilot');
console.log(roles, 'roles by copilot');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove console.log statements before merging to ensure cleaner code and avoid exposing potentially sensitive information.

@@ -815,6 +815,27 @@ const projectServiceUtils = {
}
},

getRolesByRoleName: Promise.coroutine(function* (roleName, logger, requestId) { // eslint-disable-line func-names
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using async/await instead of Promise.coroutine for better readability and modern syntax.

return _.get(res, 'data.result.content', []).map(r => r.roleName);
});
} catch (err) {
return Promise.reject(err);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of catching the error and returning a rejected promise, consider allowing the error to propagate naturally. This will make the function easier to handle with async/await syntax.

.then((opportunity) => {
console.log(opportunity);
const roles = util.getRolesByRoleName('copilot');
const roleInfo = util.getRoleInfo(roles[0]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider checking if roles is not empty before accessing roles[0] to avoid potential runtime errors.

@@ -815,6 +815,48 @@ const projectServiceUtils = {
}
},

getRoleInfo: Promise.coroutine(function* (roleId, logger, requestId) { // eslint-disable-line func-names
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using async/await syntax instead of Promise.coroutine for better readability and modern JavaScript practices.

},
}).then((res) => {
logger.debug(`Role info by ${roleId}: ${JSON.stringify(res.data.result.content)}`);
return _.get(res, 'data.result.content', []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of _.get is good for safely accessing nested properties, but ensure that lodash is imported as _ at the top of the file if not already done.

return _.get(res, 'data.result.content', []);
});
} catch (err) {
return Promise.reject(err);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of returning Promise.reject(err), consider using throw err; to maintain consistency with async/await error handling patterns.

src/util.js Outdated
},
}).then((res) => {
logger.debug(`Roles by ${roleName}: ${JSON.stringify(res.data.result.content)}`);
return _.get(res, 'data.result.content', []).map(r => r.id);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from r.roleName to r.id alters the data being returned by the map function. Ensure that this change aligns with the intended functionality and that any dependent code is updated accordingly to handle the new data structure.

@@ -52,6 +53,14 @@ module.exports = (data, existingTransaction) => {
return models.CopilotOpportunity
.create(data, { transaction });
}))
.then(async (opportunity) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider removing the console.log(opportunity); statement if it's not needed for debugging purposes. Leaving console logs in production code can lead to performance issues and cluttered logs.

console.log(opportunity);
const roles = await util.getRolesByRoleName('copilot');
const roleInfo = await util.getRoleInfo(roles[0]);
console.log(roles, roleInfo, 'roles by copilot');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider removing the console.log(roles, roleInfo, 'roles by copilot'); statement if it's not necessary for debugging. Console logs can clutter the output and should be avoided in production code.

@@ -35,7 +35,7 @@ module.exports = [
updatedBy: req.authUser.userId,
});

return approveRequest(data)
return approveRequest(req, data)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approveRequest function signature has changed to include req as a parameter. Ensure that the approveRequest function is updated to handle this new parameter correctly. If req is not needed in the function, consider removing it to maintain the original function signature.

@@ -11,7 +12,7 @@ const resolveTransaction = (transaction, callback) => {
return models.sequelize.transaction(callback);
};

module.exports = (data, existingTransaction) => {
module.exports = (req, data, existingTransaction) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature has been changed to include req as a parameter. Ensure that req is used appropriately within the function. If req is not used, consider removing it to avoid confusion.

@@ -52,6 +53,14 @@ module.exports = (data, existingTransaction) => {
return models.CopilotOpportunity
.create(data, { transaction });
}))
.then(async (opportunity) => {
console.log(opportunity);
const roles = await util.getRolesByRoleName('copilot', req.log, req.id);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function getRolesByRoleName now takes additional parameters req.log and req.id. Ensure that req is defined and available in this context to avoid potential runtime errors.

.then(async (opportunity) => {
console.log(opportunity);
const roles = await util.getRolesByRoleName('copilot', req.log, req.id);
const roleInfo = await util.getRoleInfo(roles[0], req.log, req.id);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, the function getRoleInfo now takes additional parameters req.log and req.id. Verify that req is correctly passed and accessible here to prevent any issues.

@@ -98,7 +98,7 @@ module.exports = [
updatedBy: req.authUser.userId,
type: copilotRequest.data.projectType,
});
return approveRequest(approveData, transaction).then(() => copilotRequest);
return approveRequest(req, approveData, transaction).then(() => copilotRequest);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approveRequest function signature has changed to include req as an additional parameter. Ensure that the approveRequest function is updated accordingly to handle this new parameter, and verify that all calls to approveRequest throughout the codebase are updated to match this new signature.

@@ -1,7 +1,10 @@
import _ from 'lodash';
import config from 'config';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a check to ensure that the 'config' module is correctly configured and contains the necessary properties before using it. This can help prevent runtime errors if the configuration is missing or incorrect.

import { COPILOT_REQUEST_STATUS } from '../../constants';
import { CONNECT_NOTIFICATION_EVENT, COPILOT_REQUEST_STATUS } from '../../constants';
import util from '../../util';
import { createEvent } from '../../services/busApi';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the 'createEvent' function from 'busApi' is used correctly later in the code. If not already done, verify that it handles errors and edge cases appropriately.

@@ -52,6 +55,34 @@ module.exports = (data, existingTransaction) => {
return models.CopilotOpportunity
.create(data, { transaction });
}))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console.log(opportunity); statement has been removed, which is good for production code, but ensure that any necessary logging is still in place for debugging purposes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason to use console here instead of the req.log?

@@ -52,6 +55,34 @@ module.exports = (data, existingTransaction) => {
return models.CopilotOpportunity
.create(data, { transaction });
}))
.then(async (opportunity) => {
const roles = await util.getRolesByRoleName('copilot', req.log, req.id);
const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console.log(roles, roleInfo, 'roles by copilot'); statement has been removed. Make sure that the necessary information is logged elsewhere if needed for debugging or auditing purposes.

const emailEventType = CONNECT_NOTIFICATION_EVENT.COPILOT_OPPORTUNITY_CREATED;
const copilotPortalUrl = config.get('copilotPortalUrl');
req.log.info("Sending emails to all copilots about new opportunity");
subjects.forEach(subject => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling the case where subjects might be an empty array to avoid unnecessary log entries or operations.

const copilotPortalUrl = config.get('copilotPortalUrl');
req.log.info("Sending emails to all copilots about new opportunity");
subjects.forEach(subject => {
req.log.info("Each copilot members", subject);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log message 'Each copilot members' could be more descriptive. Consider including more context, such as the copilot's handle or email, to make the logs more informative.

name: config.get('EMAIL_INVITE_FROM_NAME'),
email: config.get('EMAIL_INVITE_FROM_EMAIL'),
},
categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the category string is correctly formatted and consistent with other parts of the application to avoid issues with event categorization.

}).then((res) => {
logger.debug(`Roles by ${roleName}: ${JSON.stringify(res.data.result.content)}`);
return _.get(res, 'data.result.content', [])
.filter(item => item.roleName === roleName)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name than item to improve code readability. For example, you could use role or roleItem to indicate that this represents a role object.

try {
const token = yield this.getM2MToken();
const httpClient = this.getHttpClient({ id: requestId, log: logger });
httpClient.defaults.timeout = 6000;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting a timeout for the HTTP client is a good practice to prevent hanging requests. However, consider making the timeout value configurable through an environment variable or configuration file instead of hardcoding it. This would allow for easier adjustments in different environments or scenarios.

try {
const token = yield this.getM2MToken();
const httpClient = this.getHttpClient({ id: requestId, log: logger });
httpClient.defaults.timeout = 6000;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting a timeout for the HTTP client is a good practice to prevent hanging requests. However, ensure that 6000 milliseconds is an appropriate timeout for all expected network conditions and use cases. Consider making this configurable if different timeouts might be needed in different environments.

return _.get(res, 'data.result.content', []);
});
} catch (err) {
logger.debug(err, "error on getting role info");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive error message to provide better context for debugging. For example, include information about the operation that failed or the parameters involved.

@@ -302,6 +302,9 @@ export const CONNECT_NOTIFICATION_EVENT = {
TOPIC_UPDATED: 'connect.notification.project.topic.updated',
POST_CREATED: 'connect.notification.project.post.created',
POST_UPDATED: 'connect.notification.project.post.edited',

// External action email
EXTERNAL_ACTION_EMAIL: 'external.action.email',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from COPILOT_OPPORTUNITY_CREATED to EXTERNAL_ACTION_EMAIL seems to alter the meaning of the event. Ensure that this change aligns with the intended functionality described in the pull request. If the intention is to notify all copilots, verify that the event name accurately reflects this purpose.

const roles = await util.getRolesByRoleName('copilot', req.log, req.id);
req.log.info("getting subjects for roles", roles[0]);
const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id);
const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change of emailEventType from CONNECT_NOTIFICATION_EVENT.COPILOT_OPPORTUNITY_CREATED to CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL should be verified to ensure it aligns with the intended notification logic. This change might affect how the event is categorized or processed.

req.log.info("Each copilot members", subject);
createEvent(emailEventType, {
data: {
user_name: subject.handle,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key handle has been changed to user_name. Ensure that this change is compatible with the rest of the system and that any downstream dependencies are updated accordingly.

user_name: subject.handle,
opportunityDetailsUrl: `${copilotPortalUrl}/opportunity/${opportunity.id}`,
},
sendgrid_template_id: "d-3efdc91da580479d810c7acd50a4c17f",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The addition of sendgrid_template_id should be checked to confirm that the template ID d-3efdc91da580479d810c7acd50a4c17f is correct and that it exists in SendGrid. Also, verify that it matches the intended email format and content.

},
sendgrid_template_id: "d-3efdc91da580479d810c7acd50a4c17f",
recipients: [subject.email],
version: '433b1688-c543-4656-a295-efcbea57444d',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version field has been changed from 'v3' to '433b1688-c543-4656-a295-efcbea57444d'. Ensure that this version identifier is correct and that it corresponds to the expected versioning system or API.

const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL;
const copilotPortalUrl = config.get('copilotPortalUrl');
req.log.info("Sending emails to all copilots about new opportunity");
req.log.info(`${copilotPortalUrl}/opportunity/${opportunity.id}`, '`${copilotPortalUrl}/opportunity/${opportunity.id}`');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log statement seems to have a redundant string repetition. The second argument appears to be a duplicate of the first. Consider removing the duplicate string to avoid unnecessary repetition.

req.log.info("Each copilot members", subject);
createEvent(emailEventType, {
data: {
userName: subject.handle,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key user_name was changed to userName. Ensure that this change is reflected in all parts of the codebase where this key is used, including any documentation or external systems that might rely on the previous key format.

req.log.info("Each copilot members", subject);
createEvent(emailEventType, {
data: {
user_name: subject.handle,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using camelCase for consistency with JavaScript naming conventions. Change user_name to userName.

createEvent(emailEventType, {
data: {
user_name: subject.handle,
opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using camelCase for consistency with JavaScript naming conventions. Change opportunity_details_url to opportunityDetailsUrl.

},
sendgrid_template_id: "d-3efdc91da580479d810c7acd50a4c17f",
recipients: [subject.email],
version: 'v3',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version identifier has been changed from a specific UUID to 'v3'. Ensure that 'v3' is the correct and intended version for the sendgrid template. If 'v3' is a placeholder or not a valid version, it may cause issues in the notification process.

@@ -65,7 +66,36 @@ module.exports = [
}

return models.CopilotApplication.create(data)
.then((result) => {
.then(async (result) => {
const pmRole = await util.getRolesByRoleName('Project Manager', req.log, req.id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding error handling for the await util.getRolesByRoleName call to manage potential failures or exceptions.

.then((result) => {
.then(async (result) => {
const pmRole = await util.getRolesByRoleName('Project Manager', req.log, req.id);
const { subjects = [] } = await util.getRoleInfo(pmRole[0], req.log, req.id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding error handling for the await util.getRoleInfo call to manage potential failures or exceptions.

const pmRole = await util.getRolesByRoleName('Project Manager', req.log, req.id);
const { subjects = [] } = await util.getRoleInfo(pmRole[0], req.log, req.id);

const creator = await util.getMemberDetailsByUserIds([req.authUser.userId], req.log, req.id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding error handling for the await util.getMemberDetailsByUserIds call to manage potential failures or exceptions.

const creator = await util.getMemberDetailsByUserIds([req.authUser.userId], req.log, req.id);
const listOfSubjects = subjects;
if (creator) {
const isCreatorPartofSubjects = subjects.find(item => item.email === creator[0].email);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable isCreatorPartofSubjects could be renamed to isCreatorPartOfSubjects to follow camelCase convention.

const isCreatorPartofSubjects = subjects.find(item => item.email === creator[0].email);
if (!isCreatorPartofSubjects) {
listOfSubjects.push({
email: creator.email,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a potential issue here: creator.email should be creator[0].email to match the structure used in the isCreatorPartofSubjects check.


const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL;
const copilotPortalUrl = config.get('copilotPortalUrl');
listOfSubjects.forEach((subject) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding error handling for the createEvent function call to manage potential failures or exceptions.

@@ -52,6 +55,29 @@ module.exports = (data, existingTransaction) => {
return models.CopilotOpportunity
.create(data, { transaction });
}))
.then(async (opportunity) => {
const roles = await util.getRolesByRoleName('copilot', req.log, req.id);
const { subjects = [] } = await util.getRoleInfo(roles[0], req.log, req.id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log message for getting subjects for roles has been removed. If this was intentional, ensure that the removal does not affect the debugging process. If not, consider reinstating it for better traceability.

const copilotPortalUrl = config.get('copilotPortalUrl');
req.log.info("Sending emails to all copilots about new opportunity");
subjects.forEach(subject => {
req.log.info("Each copilot members", subject);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log message for the copilot portal URL has been removed. If this was intentional, ensure that the removal does not affect the ability to trace the URL being used. If not, consider reinstating it for better traceability.

const pmRole = await util.getRolesByRoleName('Project Manager', req.log, req.id);
const { subjects = [] } = await util.getRoleInfo(pmRole[0], req.log, req.id);

const creator = await util.getMemberDetailsByUserIds([opportunity.userId], req.log, req.id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from req.authUser.userId to opportunity.userId may affect the logic of fetching the creator's details. Ensure that opportunity.userId correctly represents the intended user in the context of notifying all copilots.

if (creator) {
const isCreatorPartofSubjects = subjects.find(item => item.email === creator[0].email);
if (!isCreatorPartofSubjects) {
listOfSubjects.push({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like creator is being accessed as an array with creator[0], but previously it was accessed as an object with creator.email. Ensure that creator is consistently treated as an array or an object throughout the code to avoid potential runtime errors.

@hentrymartin hentrymartin requested a review from kkartunov June 10, 2025 09:13
@@ -65,7 +67,36 @@ module.exports = [
}

return models.CopilotApplication.create(data)
.then((result) => {
.then(async (result) => {
const pmRole = await util.getRolesByRoleName('Project Manager', req.log, req.id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use enum/constant for the role name. Hardcoding it isn't good practice.

const creator = await util.getMemberDetailsByUserIds([opportunity.userId], req.log, req.id);
const listOfSubjects = subjects;
if (creator) {
const isCreatorPartofSubjects = subjects.find(item => item.email === creator[0].email);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we compare lower case values here? Coul be some issues if we are not.

user_name: subject.handle,
opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}#applications`,
},
sendgrid_template_id: "d-d7c1f48628654798a05c8e09e52db14f",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please conver sendgrid remplate id to constant.

@@ -52,6 +55,29 @@ module.exports = (data, existingTransaction) => {
return models.CopilotOpportunity
.create(data, { transaction });
}))
.then(async (opportunity) => {
const roles = await util.getRolesByRoleName('copilot', req.log, req.id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conver role name to enum/constant. Do not use hardcoded names.

user_name: subject.handle,
opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`,
},
sendgrid_template_id: "d-3efdc91da580479d810c7acd50a4c17f",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the template id to constant.

export const TEMPLATE_IDS = {
APPLY_COPILOT: 'd-d7c1f48628654798a05c8e09e52db14f',
CREATE_REQUEST: 'd-3efdc91da580479d810c7acd50a4c17f'
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a semicolon at the end of the object declaration for consistency with the rest of the codebase.

@@ -65,7 +67,36 @@ module.exports = [
}

return models.CopilotApplication.create(data)
.then((result) => {
.then(async (result) => {
const pmRole = await util.getRolesByRoleName(USER_ROLE.PROJECT_MANAGER, req.log, req.id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from a string to a constant USER_ROLE.PROJECT_MANAGER is a good practice for maintainability, but ensure that USER_ROLE is properly imported or defined in this file.

const creator = await util.getMemberDetailsByUserIds([opportunity.userId], req.log, req.id);
const listOfSubjects = subjects;
if (creator) {
const isCreatorPartofSubjects = subjects.find(item => item.email.toLowerCase() === creator[0].email.toLowerCase());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converting emails to lowercase for comparison is a good practice to ensure case-insensitive matching. However, ensure that both subjects and creator arrays are consistently handling email casing throughout the application to avoid any potential mismatches.

@@ -52,6 +55,28 @@ module.exports = (data, existingTransaction) => {
return models.CopilotOpportunity
.create(data, { transaction });
}))
.then(async (opportunity) => {
const roles = await util.getRolesByRoleName(USER_ROLE.TC_COPILOT, req.log, req.id);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from a string 'copilot' to USER_ROLE.TC_COPILOT is good for consistency and maintainability, but ensure that USER_ROLE.TC_COPILOT is defined and correctly imported in this file.

user_name: subject.handle,
opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`,
},
sendgrid_template_id: TEMPLATE_IDS.CREATE_REQUEST,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change to use TEMPLATE_IDS.CREATE_REQUEST instead of a hardcoded string is a good practice for maintainability. Ensure that TEMPLATE_IDS.CREATE_REQUEST is defined and correctly imported in this file.

const copilotPortalUrl = config.get('copilotPortalUrl');
req.log.info("Sending emails to all copilots about new opportunity");
subjects.forEach(subject => {
createEvent(emailEventType, {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log statement 'Each copilot members' was removed. If this was intentional, ensure that the logging is still sufficient for debugging purposes. If not, consider adding a relevant log statement to track each copilot being notified.

Copy link
Contributor

@kkartunov kkartunov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

@hentrymartin hentrymartin merged commit 6f4cb68 into develop Jun 10, 2025
2 checks passed
@hentrymartin hentrymartin deleted the pm-1173 branch June 10, 2025 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants