Skip to content

Supporting Release For Connect 2.4.12 #138

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 46 commits into from
Jun 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
430d753
updated image link to point to tc-connect static resorces in email te…
akumar1503 May 11, 2019
fe5d3b0
update template.html
akumar1503 May 12, 2019
cd5b535
part of the winning submission from challenge 30090377 - Topcoder Not…
maxceem May 13, 2019
44a54b5
part of the winning submission from challenge 30090377 - Topcoder Not…
maxceem May 13, 2019
0cb0027
enable lint validation during deploying
maxceem May 13, 2019
8e5c9d9
Merge pull request #117 from maxceem/feature/skip-notifications-for-c…
May 13, 2019
febce84
Github issue#115, Missing notifications
May 15, 2019
1e6be72
Merge branch 'dev' into hotfix/handling_unhandled_error_in_getting_me…
May 15, 2019
7a1e5e9
Merge pull request #118 from topcoder-platform/hotfix/handling_unhand…
May 15, 2019
89bf919
Error logging
May 15, 2019
c8f6782
Merge branch 'hotfix/handling_unhandled_error_in_getting_mentioned_us…
May 15, 2019
d3aa503
Merge pull request #119 from topcoder-platform/hotfix/handling_unhand…
May 15, 2019
6583bbc
Resolve the promise in case of error for fetching details for mention…
May 15, 2019
1d414fc
Merge pull request #120 from topcoder-platform/hotfix/handling_unhand…
May 15, 2019
20024e3
Logging in health endpoint
May 16, 2019
6321083
Temporary disabling the fix to reproduce the error on dev
May 16, 2019
4cba87a
Merge pull request #122 from topcoder-platform/hotfix/handling_unhand…
May 16, 2019
0680ba7
properly quote and escape query to member service
maxceem May 16, 2019
b99f699
Merge branch 'dev' into hotfix/handling_unhandled_error_in_getting_me…
May 16, 2019
9258b81
Merge pull request #126 from topcoder-platform/hotfix/handling_unhand…
May 16, 2019
3ccf1c6
Merge pull request #124 from maxceem/feature/fix-es-query-escaping
May 16, 2019
6819b5c
Testing new improved health check
May 20, 2019
0da69ca
Deployable feature branch
May 20, 2019
7c1bc9f
Testing new improved health check
May 20, 2019
029c700
Removed using explicit constructor anti pattern which was breaking th…
May 20, 2019
667441e
Better to not break promise chain
May 20, 2019
566fe4f
Aborting the process on any unhandled rejection to let the container …
May 20, 2019
230cf6d
Avoiding usage of explicit constructor anti pattern and instead use l…
May 20, 2019
2c18431
Adding back the fix for escaping the reserved keywords of elastic search
May 20, 2019
7213c7d
Removed feature branch from deployable branches list
May 20, 2019
67b76fe
implement v5 API
mfikria May 21, 2019
bc9f18c
Merge pull request #128 from topcoder-platform/feature/test_health_check
May 21, 2019
0d01c60
Fixing one more Unhandled promise rejection because of wrong data typ…
May 23, 2019
28161f7
Instead of aborting the process we now let health check to convey the…
May 23, 2019
d5e9ac5
Merge pull request #131 from mfikria/dev
May 24, 2019
a0ce50a
Merge pull request #132 from topcoder-platform/feature/V5-API-Standards
May 27, 2019
d53dfed
Revert "V5 API standards"
May 27, 2019
76057d9
Merge pull request #133 from topcoder-platform/revert-132-feature/V5-…
May 27, 2019
b1fad0f
supporting 'limit' filed in query string temporarily.
May 27, 2019
4ddeb72
Merge branch 'dev' into feature/V5-API-Standards
May 27, 2019
ffc8ef0
Merge pull request #134 from topcoder-platform/feature/V5-API-Standards
May 27, 2019
9632135
patch for v5 api standards
May 29, 2019
3ef5a5f
Merge pull request #136 from topcoder-platform/hotfix/V5-API-Standards
May 30, 2019
9be1928
setting old response format
May 30, 2019
acab928
Merge pull request #137 from topcoder-platform/hotfix/V5-API-Standards
May 30, 2019
891d4b8
Merge pull request #116 from akumar1503/update_email_image_links
Jun 3, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ workflows:
context : org-global
filters:
branches:
only: [dev, 'feature/general-purpose-notifications-usage']
only: [dev, 'hotfix/V5-API-Standards']
- "build-prod":
context : org-global
filters:
Expand Down
34 changes: 18 additions & 16 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module.exports = {
TC_API_V4_BASE_URL: process.env.TC_API_V4_BASE_URL || '',
TC_API_V5_BASE_URL: process.env.TC_API_V5_BASE_URL || '',
API_CONTEXT_PATH: process.env.API_CONTEXT_PATH || '/v5/notifications',
TC_API_BASE_URL: process.env.TC_API_BASE_URL || '',

// Configuration for generating machine to machine auth0 token.
// The token will be used for calling another internal API.
Expand All @@ -45,56 +46,57 @@ module.exports = {
AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL,

KAFKA_CONSUMER_RULESETS: {
// key is Kafka topic name, value is array of ruleset which have key as handler function name defined in src/processors/index.js
// key is Kafka topic name, value is array of ruleset which have key as
// handler function name defined in src/processors/index.js
'challenge.notification.events': [
{
handleChallenge: /** topic handler name */
{
type: 'UPDATE_DRAFT_CHALLENGE',
roles: ["Submitter" /** Competitor */, "Copilot", "Reviewer"],
roles: ['Submitter' /** Competitor */, 'Copilot', 'Reviewer'],
notification:
{
id: 0, /** challengeid or projectid */
name: '', /** challenge name */
group: 'Challenge',
title: 'Challenge specification is modified.'
}
}
}
title: 'Challenge specification is modified.',
},
},
},
],
'notifications.autopilot.events': [
{
handleAutoPilot:
{
phaseTypeName: 'Checkpoint Screening',
state: 'START',
roles: ["Copilot", "Reviewer"],
roles: ['Copilot', 'Reviewer'],
notification:
{
id: 0, /** challengeid or projectid */
name: '', /** challenge name */
group: 'Challenge',
title: 'Challenge checkpoint review.'
}
}
}
title: 'Challenge checkpoint review.',
},
},
},
],
'submission.notification.create': [
{
handleSubmission:
{
resource: 'submission',
roles: ["Copilot", "Reviewer"],
roles: ['Copilot', 'Reviewer'],
selfOnly: true /** Submitter only */,
notification:
{
id: 0, /** challengeid or projectid */
name: '', /** challenge name */
group: 'Submission',
title: 'A new submission is uploaded.'
}
}
}
title: 'A new submission is uploaded.',
},
},
},
],
//'notifications.community.challenge.created': ['handleChallengeCreated'],
//'notifications.community.challenge.phasewarning': ['handleChallengePhaseWarning'],
Expand Down
134 changes: 109 additions & 25 deletions connect/connectNotificationServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const config = require('./config');
const notificationServer = require('../index');
const _ = require('lodash');
const service = require('./service');
const helpers = require('./helpers');
const { BUS_API_EVENT } = require('./constants');
const EVENTS = require('./events-config').EVENTS;
const PROJECT_ROLE_RULES = require('./events-config').PROJECT_ROLE_RULES;
Expand Down Expand Up @@ -85,27 +86,28 @@ const getNotificationsForMentionedUser = (logger, eventConfig, content) => {
// only one per userHandle
notifications = _.uniqBy(notifications, 'userHandle');

return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
const handles = _.map(notifications, 'userHandle');
if (handles.length > 0) {
service.getUsersByHandle(handles).then((users) => {
_.forEach(notifications, (notification) => {
const mentionedUser = _.find(users, { handle: notification.userHandle });
notification.userId = mentionedUser ? mentionedUser.userId.toString() : notification.userHandle;
});
resolve(notifications);
}).catch((error) => {
if (logger) {
logger.error(error);
logger.info('Unable to send notification to mentioned user')
const handles = _.map(notifications, 'userHandle');
if (handles.length > 0) {
return service.getUsersByHandle(handles).then((users) => {
_.forEach(notifications, (notification) => {
const mentionedUser = _.find(users, { handle: notification.userHandle });
notification.userId = mentionedUser ? mentionedUser.userId.toString() : null;
if (!notification.userId && logger) {// such notifications would be discarded later after aggregation
logger.info(`Unable to find user with handle ${notification.userHandle}`);
}
//resolves with empty notification which essentially means we are unable to send notification to mentioned user
resolve([]);
});
} else {
resolve([]);
}
});
return Promise.resolve(notifications);
}).catch((error) => {
if (logger) {
logger.error(error);
logger.info('Unable to send notification to mentioned user');
}
//resolves with empty notification which essentially means we are unable to send notification to mentioned user
return Promise.resolve([]);
});
} else {
return Promise.resolve([]);
}
};

/**
Expand Down Expand Up @@ -150,7 +152,7 @@ const getProjectMembersNotifications = (eventConfig, project) => {
return Promise.resolve([]);
}

return new Promise((resolve) => {
return Promise.promisify((callback) => {
let notifications = [];
const projectMembers = _.get(project, 'members', []);

Expand Down Expand Up @@ -185,8 +187,8 @@ const getProjectMembersNotifications = (eventConfig, project) => {
// only one per userId
notifications = _.uniqBy(notifications, 'userId');

resolve(notifications);
});
callback(null, notifications);
})();
};

/**
Expand Down Expand Up @@ -254,6 +256,83 @@ const getNotificationsForTopicStarter = (eventConfig, topicId) => {
});
};

/**
* Filter members by project roles
*
* @params {Array} List of project roles
* @params {Array} List of project members
*
* @returns {Array} List of objects with user ids
*/
const filterMembersByRoles = (roles, members) => {
let result = [];

roles.forEach(projectRole => {
result = result.concat(
_.filter(members, PROJECT_ROLE_RULES[projectRole])
.map(projectMember => ({
userId: projectMember.userId.toString(),
}))
);
});

return result;
};

/**
* Exclude private posts notification
*
* @param {Object} eventConfig event configuration
* @param {Object} project project details
* @param {Array} tags list of message tags
*
* @return {Promise} resolves to a list of notifications
*/
const getExcludedPrivatePostNotifications = (eventConfig, project, tags) => {
// skip if message is not private or exclusion rule is not configured
if (!_.includes(tags, 'MESSAGES') || !eventConfig.privatePostsForProjectRoles) {
return Promise.resolve([]);
}

const members = _.get(project, 'members', []);
const notifications = filterMembersByRoles(eventConfig.privatePostsForProjectRoles, members);

return Promise.resolve(notifications);
};

/**
* Exclude notifications about posts inside draft phases
*
* @param {Object} eventConfig event configuration
* @param {Object} project project details
* @param {Array} tags list of message tags
*
* @return {Promise} resolves to a list of notifications
*/
const getExcludeDraftPhasesNotifications = (eventConfig, project, tags) => {
// skip is no exclusion rule is configured
if (!eventConfig.draftPhasesForProjectRoles) {
return Promise.resolve([]);
}

const phaseId = helpers.extractPhaseId(tags);
// skip if it is not phase notification
if (!phaseId) {
return Promise.resolve([]);
}

// exclude all user with configured roles if phase is in draft state
return service.getPhase(project.id, phaseId)
.then((phase) => {
if (phase.status === 'draft') {
const members = _.get(project, 'members', []);
const notifications = filterMembersByRoles(eventConfig.draftPhasesForProjectRoles, members);

return Promise.resolve(notifications);
}
});
};

/**
* Exclude notifications using exclude rules of the event config
*
Expand Down Expand Up @@ -281,12 +360,17 @@ const excludeNotifications = (logger, notifications, eventConfig, message, data)
// and after filter out such notifications from the notifications list
// TODO move this promise all together with `_.uniqBy` to one function
// and reuse it here and in `handler` function
const tags = _.get(message, 'tags', []);

return Promise.all([
getNotificationsForTopicStarter(excludeEventConfig, message.topicId),
getNotificationsForUserId(excludeEventConfig, message.userId),
getNotificationsForMentionedUser(logger, excludeEventConfig, message.postContent),
getProjectMembersNotifications(excludeEventConfig, project),
getTopCoderMembersNotifications(excludeEventConfig),
// these are special exclude rules which are only working for excluding notifications but not including
getExcludedPrivatePostNotifications(excludeEventConfig, project, tags),
getExcludeDraftPhasesNotifications(excludeEventConfig, project, tags),
]).then((notificationsPerSource) => (
_.uniqBy(_.flatten(notificationsPerSource), 'userId')
)).then((excludedNotifications) => {
Expand Down Expand Up @@ -332,10 +416,10 @@ const handler = (topic, message, logger, callback) => {
}

// get project details
service.getProject(projectId).then(project => {
return service.getProject(projectId).then(project => {
let allNotifications = [];

Promise.all([
return Promise.all([
// the order in this list defines the priority of notification for the SAME user
// upper in this list - higher priority
// NOTE: always add all handles here, they have to check by themselves:
Expand All @@ -357,7 +441,7 @@ const handler = (topic, message, logger, callback) => {
project,
})
)).then((notifications) => {
allNotifications = _.filter(notifications, notification => notification.userId !== `${message.initiatorUserId}`);
allNotifications = _.filter(notifications, n => n.userId && n.userId !== `${message.initiatorUserId}`);

if (eventConfig.includeUsers && message[eventConfig.includeUsers] &&
message[eventConfig.includeUsers].length > 0) {
Expand Down
25 changes: 25 additions & 0 deletions connect/events-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { BUS_API_EVENT } = require('./constants');

// project member role names
const PROJECT_ROLE_OWNER = 'owner';
const PROJECT_ROLE_CUSTOMER = 'customer';
const PROJECT_ROLE_COPILOT = 'copilot';
const PROJECT_ROLE_MANAGER = 'manager';
const PROJECT_ROLE_MEMBER = 'member';
Expand All @@ -13,13 +14,15 @@ const PROJECT_ROLE_ACCOUNT_MANAGER = 'account_manager';
// project member role rules
const PROJECT_ROLE_RULES = {
[PROJECT_ROLE_OWNER]: { role: 'customer', isPrimary: true },
[PROJECT_ROLE_CUSTOMER]: { role: 'customer' },
[PROJECT_ROLE_COPILOT]: { role: 'copilot' },
[PROJECT_ROLE_MANAGER]: { role: 'manager' },
[PROJECT_ROLE_ACCOUNT_MANAGER]: { role: 'account_manager' },
[PROJECT_ROLE_MEMBER]: {},
};

// TopCoder roles
// eslint-disable-next-line no-unused-vars
const ROLE_CONNECT_COPILOT = 'Connect Copilot';
const ROLE_CONNECT_MANAGER = 'Connect Manager';
const ROLE_CONNECT_COPILOT_MANAGER = 'Connect Copilot Manager';
Expand Down Expand Up @@ -123,31 +126,53 @@ const EVENTS = [
version: 2,
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
toMentionedUsers: true,
exclude: {
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
},
}, {
type: BUS_API_EVENT.CONNECT.POST.CREATED,
version: 2,
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
toTopicStarter: true,
toMentionedUsers: true,
exclude: {
draftPhasesForProjectRoles: [PROJECT_ROLE_CUSTOMER],
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
},
}, {
type: BUS_API_EVENT.CONNECT.POST.UPDATED,
version: 2,
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
toTopicStarter: true,
toMentionedUsers: true,
exclude: {
draftPhasesForProjectRoles: [PROJECT_ROLE_CUSTOMER],
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
},
}, {
type: BUS_API_EVENT.CONNECT.POST.MENTION,
exclude: {
draftPhasesForProjectRoles: [PROJECT_ROLE_CUSTOMER],
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
},
},
{
type: BUS_API_EVENT.CONNECT.TOPIC.DELETED,
version: 2,
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
toTopicStarter: false,
exclude: {
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
},
},
{
type: BUS_API_EVENT.CONNECT.POST.DELETED,
version: 2,
projectRoles: [PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_MEMBER],
exclude: {
draftPhasesForProjectRoles: [PROJECT_ROLE_CUSTOMER],
privatePostsForProjectRoles: [PROJECT_ROLE_CUSTOMER],
},
},
{
type: BUS_API_EVENT.CONNECT.PROJECT.LINK_CREATED,
Expand Down
16 changes: 16 additions & 0 deletions connect/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
* Helper functions
*/
const Remarkable = require('remarkable');
const _ = require('lodash');

const PHASE_ID_REGEXP = /phase#(\d+)/;

/**
* Convert markdown into raw draftjs state
Expand Down Expand Up @@ -42,7 +45,20 @@ const sanitizeEmail = (email) => {
return '';
};

/**
* Helper method to extract phaseId from tag
*
* @param {Array} tags list of message tags
*
* @returns {String} phase id
*/
const extractPhaseId = (tags) => {
const phaseIds = tags.map((tag) => _.get(tag.match(PHASE_ID_REGEXP), '1', null));
return _.find(phaseIds, (phaseId) => phaseId !== null);
};

module.exports = {
markdownToHTML,
sanitizeEmail,
extractPhaseId,
};
Loading