Skip to content

Commit 593f46c

Browse files
author
vikasrohit
authored
Merge pull request #184 from topcoder-platform/feature/milestone_phase_events_separation_squashed
Separated the raising of project and phase events from timeline/milestone changes
2 parents b06e8a5 + 80c7897 commit 593f46c

File tree

12 files changed

+345
-61
lines changed

12 files changed

+345
-61
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ workflows:
7676
- test
7777
filters:
7878
branches:
79-
only: ['dev', 'feature/timeline-milestone']
79+
only: ['dev']
8080
- deployProd:
8181
requires:
8282
- test

src/constants.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,14 @@ export const BUS_API_EVENT = {
101101

102102
// When milestone is added/deleted to/from the phase,
103103
// When milestone is updated for duration/startDate/endDate/status
104-
TIMELINE_MODIFIED: 'notifications.connect.project.phase.timelineModified',
104+
TIMELINE_ADJUSTED: 'notifications.connect.project.phase.timeline.adjusted',
105105

106106
// When specification of a product is modified
107107
PROJECT_PRODUCT_SPECIFICATION_MODIFIED: 'notifications.connect.project.productSpecificationModified',
108108

109+
MILESTONE_ADDED: 'notifications.connect.project.phase.milestone.added',
110+
MILESTONE_REMOVED: 'notifications.connect.project.phase.milestone.removed',
111+
MILESTONE_UPDATED: 'notifications.connect.project.phase.milestone.updated',
109112
// When milestone is marked as active
110113
MILESTONE_TRANSITION_ACTIVE: 'notifications.connect.project.phase.milestone.transition.active',
111114
// When milestone is marked as completed

src/events/busApi.js

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,22 @@ module.exports = (app, logger) => {
474474
* @param {Object} original the original milestone
475475
* @param {Object} updated the updated milestone
476476
* @param {Object} project the project
477+
* @param {Object} timeline the updated timeline
477478
* @returns {Promise<void>} void
478479
*/
479-
function sendMilestoneNotification(req, original, updated, project) {
480+
function sendMilestoneNotification(req, original, updated, project, timeline) {
480481
logger.debug('sendMilestoneNotification', original, updated);
482+
// throw generic milestone updated bus api event
483+
createEvent(BUS_API_EVENT.MILESTONE_UPDATED, {
484+
projectId: project.id,
485+
projectName: project.name,
486+
projectUrl: connectProjectUrl(project.id),
487+
timeline,
488+
originalMilestone: original,
489+
updatedMilestone: updated,
490+
userId: req.authUser.userId,
491+
initiatorUserId: req.authUser.userId,
492+
}, logger);
481493
// Send transition events
482494
if (original.status !== updated.status) {
483495
let event;
@@ -492,8 +504,7 @@ module.exports = (app, logger) => {
492504
projectId: project.id,
493505
projectName: project.name,
494506
projectUrl: connectProjectUrl(project.id),
495-
timelineId: req.timeline.id,
496-
timelineName: req.timeline.name,
507+
timeline,
497508
originalMilestone: original,
498509
updatedMilestone: updated,
499510
userId: req.authUser.userId,
@@ -510,8 +521,7 @@ module.exports = (app, logger) => {
510521
projectId: project.id,
511522
projectName: project.name,
512523
projectUrl: connectProjectUrl(project.id),
513-
timelineId: req.timeline.id,
514-
timelineName: req.timeline.name,
524+
timeline,
515525
originalMilestone: original,
516526
updatedMilestone: updated,
517527
userId: req.authUser.userId,
@@ -533,15 +543,16 @@ module.exports = (app, logger) => {
533543
})
534544
.then((project) => {
535545
if (project) {
536-
createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, {
546+
createEvent(BUS_API_EVENT.MILESTONE_ADDED, {
537547
projectId,
538548
projectName: project.name,
539549
projectUrl: connectProjectUrl(projectId),
550+
addedMilestone: created,
540551
userId: req.authUser.userId,
541552
initiatorUserId: req.authUser.userId,
542553
}, logger);
543554
}
544-
sendMilestoneNotification(req, {}, created, project);
555+
// sendMilestoneNotification(req, {}, created, project);
545556
})
546557
.catch(err => null); // eslint-disable-line no-unused-vars
547558
});
@@ -551,60 +562,54 @@ module.exports = (app, logger) => {
551562
*/
552563
// eslint-disable-next-line no-unused-vars
553564
app.on(EVENT.ROUTING_KEY.MILESTONE_UPDATED, ({ req, original, updated, cascadedUpdates }) => {
554-
logger.debug('receive MILESTONE_UPDATED event');
565+
logger.debug(`receive MILESTONE_UPDATED event for milestone ${original.id}`);
555566

556567
const projectId = _.parseInt(req.params.projectId);
568+
const timeline = _.omit(req.timeline.toJSON(), 'deletedAt', 'deletedBy');
557569

558570
models.Project.findOne({
559571
where: { id: projectId },
560572
})
561-
.then((project) => {
562-
// send PROJECT_UPDATED Kafka message when one of the specified below properties changed
563-
const watchProperties = ['startDate', 'endDate', 'duration', 'details', 'status', 'order'];
564-
if (!_.isEqual(_.pick(original, watchProperties),
565-
_.pick(updated, watchProperties))) {
566-
createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, {
567-
projectId,
568-
projectName: project.name,
569-
projectUrl: connectProjectUrl(projectId),
570-
userId: req.authUser.userId,
571-
initiatorUserId: req.authUser.userId,
572-
}, logger);
573-
}
574-
sendMilestoneNotification(req, original, updated, project);
573+
.then((project) => {
574+
logger.debug(`Found project with id ${projectId}`);
575+
return models.Milestone.getTimelineDuration(timeline.id)
576+
.then(({ duration, progress }) => {
577+
timeline.duration = duration;
578+
timeline.progress = progress;
579+
sendMilestoneNotification(req, original, updated, project, timeline);
575580

576581
logger.debug('cascadedUpdates', cascadedUpdates);
577582
if (cascadedUpdates && cascadedUpdates.milestones && cascadedUpdates.milestones.length > 0) {
578583
_.each(cascadedUpdates.milestones, cascadedUpdate =>
579-
sendMilestoneNotification(req, cascadedUpdate.original, cascadedUpdate.updated, project),
584+
sendMilestoneNotification(req, cascadedUpdate.original, cascadedUpdate.updated, project, timeline),
580585
);
581586
}
582587

583588
// if timeline is modified
584589
if (cascadedUpdates && cascadedUpdates.timeline) {
585-
const timeline = cascadedUpdates.timeline;
586-
// if endDate of the timeline is modified, raise TIMELINE_MODIFIED event
587-
if (timeline.original.endDate !== timeline.updated.endDate) {
590+
const cTimeline = cascadedUpdates.timeline;
591+
// if endDate of the timeline is modified, raise TIMELINE_ADJUSTED event
592+
if (cTimeline.original.endDate !== cTimeline.updated.endDate) {
588593
// Raise Timeline changed event
589-
createEvent(BUS_API_EVENT.TIMELINE_MODIFIED, {
594+
createEvent(BUS_API_EVENT.TIMELINE_ADJUSTED, {
590595
projectId: project.id,
591596
projectName: project.name,
592597
projectUrl: connectProjectUrl(project.id),
593-
original: timeline.original,
594-
updated: timeline.updated,
598+
originalTimeline: cTimeline.original,
599+
updatedTimeline: cTimeline.updated,
595600
userId: req.authUser.userId,
596601
initiatorUserId: req.authUser.userId,
597602
}, logger);
598603
}
599604
}
600-
})
601-
.catch(err => null); // eslint-disable-line no-unused-vars
605+
});
606+
}).catch(err => null); // eslint-disable-line no-unused-vars
602607
});
603608

604609
/**
605610
* MILESTONE_REMOVED.
606611
*/
607-
app.on(EVENT.ROUTING_KEY.MILESTONE_REMOVED, ({ req }) => {
612+
app.on(EVENT.ROUTING_KEY.MILESTONE_REMOVED, ({ req, deleted }) => {
608613
logger.debug('receive MILESTONE_REMOVED event');
609614
// req.params.projectId is set by validateTimelineIdParam middleware
610615
const projectId = _.parseInt(req.params.projectId);
@@ -614,10 +619,11 @@ module.exports = (app, logger) => {
614619
})
615620
.then((project) => {
616621
if (project) {
617-
createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, {
622+
createEvent(BUS_API_EVENT.MILESTONE_REMOVED, {
618623
projectId,
619624
projectName: project.name,
620625
projectUrl: connectProjectUrl(projectId),
626+
removedMilestone: deleted,
621627
userId: req.authUser.userId,
622628
initiatorUserId: req.authUser.userId,
623629
}, logger);
@@ -639,10 +645,12 @@ module.exports = (app, logger) => {
639645
})
640646
.then((project) => {
641647
if (project) {
642-
createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, {
648+
createEvent(BUS_API_EVENT.TIMELINE_ADJUSTED, {
643649
projectId,
644650
projectName: project.name,
645651
projectUrl: connectProjectUrl(projectId),
652+
originalTimeline: original,
653+
updatedTimeline: updated,
646654
userId: req.authUser.userId,
647655
initiatorUserId: req.authUser.userId,
648656
}, logger);

src/events/index.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,18 @@ import { projectPhaseAddedHandler, projectPhaseRemovedHandler,
1010
projectPhaseUpdatedHandler } from './projectPhases';
1111
import { phaseProductAddedHandler, phaseProductRemovedHandler,
1212
phaseProductUpdatedHandler } from './phaseProducts';
13-
import { timelineAddedHandler, timelineUpdatedHandler, timelineRemovedHandler } from './timelines';
14-
import { milestoneAddedHandler, milestoneUpdatedHandler, milestoneRemovedHandler } from './milestones';
13+
import {
14+
timelineAddedHandler,
15+
timelineUpdatedHandler,
16+
timelineRemovedHandler,
17+
timelineAdjustedKafkaHandler,
18+
} from './timelines';
19+
import {
20+
milestoneAddedHandler,
21+
milestoneUpdatedHandler,
22+
milestoneRemovedHandler,
23+
milestoneUpdatedKafkaHandler,
24+
} from './milestones';
1525

1626
export const rabbitHandlers = {
1727
'project.initial': projectCreatedHandler,
@@ -56,4 +66,8 @@ export const kafkaHandlers = {
5666
[BUS_API_EVENT.TOPIC_UPDATED]: projectUpdatedKafkaHandler,
5767
[BUS_API_EVENT.POST_CREATED]: projectUpdatedKafkaHandler,
5868
[BUS_API_EVENT.POST_UPDATED]: projectUpdatedKafkaHandler,
69+
70+
// Events coming from timeline/milestones (considering it as a separate module/service in future)
71+
[BUS_API_EVENT.MILESTONE_TRANSITION_COMPLETED]: milestoneUpdatedKafkaHandler,
72+
[BUS_API_EVENT.TIMELINE_ADJUSTED]: timelineAdjustedKafkaHandler,
5973
};

src/events/milestones/index.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
*/
44
import config from 'config';
55
import _ from 'lodash';
6+
import Joi from 'joi';
67
import Promise from 'bluebird';
78
import util from '../../util';
9+
// import { createEvent } from '../../services/busApi';
10+
import { EVENT, TIMELINE_REFERENCES, MILESTONE_STATUS, REGEX } from '../../constants';
11+
import models from '../../models';
812

913
const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName');
1014
const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType');
@@ -154,9 +158,105 @@ const milestoneRemovedHandler = Promise.coroutine(function* (logger, msg, channe
154158
}
155159
});
156160

161+
/**
162+
* Kafka event handlers
163+
*/
164+
165+
const payloadSchema = Joi.object().keys({
166+
projectId: Joi.number().integer().positive().required(),
167+
projectName: Joi.string().optional(),
168+
projectUrl: Joi.string().regex(REGEX.URL).optional(),
169+
userId: Joi.number().integer().positive().required(),
170+
initiatorUserId: Joi.number().integer().positive().required(),
171+
}).unknown(true).required();
172+
173+
const findProjectPhaseProduct = function (logger, productId, raw = true) { // eslint-disable-line func-names
174+
let product;
175+
return models.PhaseProduct.findOne({
176+
where: { id: productId },
177+
raw,
178+
}).then((_product) => {
179+
logger.debug('_product', _product);
180+
if (_product) {
181+
product = _product;
182+
const phaseId = product.phaseId;
183+
const projectId = product.projectId;
184+
return Promise.all([
185+
models.ProjectPhase.findOne({
186+
where: { id: phaseId, projectId },
187+
raw,
188+
}),
189+
models.Project.findOne({
190+
where: { id: projectId },
191+
raw,
192+
}),
193+
]);
194+
}
195+
return Promise.reject('Unable to find product');
196+
}).then((projectAndPhase) => {
197+
logger.debug('projectAndPhase', projectAndPhase);
198+
if (projectAndPhase) {
199+
const phase = projectAndPhase[0];
200+
const project = projectAndPhase[1];
201+
return Promise.resolve({ product, phase, project });
202+
}
203+
return Promise.reject('Unable to find phase/project');
204+
});
205+
};
206+
207+
/**
208+
* Raises the project plan modified event
209+
* @param {Object} app Application object used to interact with RMQ service
210+
* @param {String} topic Kafka topic
211+
* @param {Object} payload Message payload
212+
* @return {Promise} Promise
213+
*/
214+
async function milestoneUpdatedKafkaHandler(app, topic, payload) {
215+
app.logger.info(`Handling Kafka event for ${topic}`);
216+
// Validate payload
217+
const result = Joi.validate(payload, payloadSchema);
218+
if (result.error) {
219+
throw new Error(result.error);
220+
}
221+
222+
const timeline = payload.timeline;
223+
// process only if timeline is related to a product reference
224+
if (timeline && timeline.reference === TIMELINE_REFERENCES.PRODUCT) {
225+
const productId = timeline.referenceId;
226+
const original = payload.originalMilestone;
227+
const updated = payload.updatedMilestone;
228+
app.logger.debug('Calling findProjectPhaseProduct');
229+
const { project, phase } = await findProjectPhaseProduct(app.logger, productId, false);
230+
app.logger.debug('Successfully fetched project, phase and product');
231+
if (original.status !== updated.status) {
232+
if (updated.status === MILESTONE_STATUS.COMPLETED) {
233+
app.logger.debug('Found milestone status to be completed');
234+
app.logger.debug(`Duration: ${timeline.duration}`);
235+
if (!isNaN(timeline.duration) && !isNaN(timeline.progress)) {
236+
app.logger.debug(`Current phase progress ${phase.progress} and duration ${phase.duration}`);
237+
const updatedPhase = await phase.update({
238+
progress: timeline.progress,
239+
duration: timeline.duration,
240+
}, ['progress', 'duration']);
241+
app.logger.debug(`Updated phase progress ${timeline.progress} and duration ${timeline.duration}`);
242+
app.logger.debug('Raising node event for PROJECT_PHASE_UPDATED');
243+
app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, {
244+
req: {
245+
params: { projectId: project.id, phaseId: phase.id },
246+
authUser: { userId: payload.userId },
247+
},
248+
original: phase,
249+
updated: _.omit(updatedPhase.toJSON(), 'deletedAt', 'deletedBy'),
250+
});
251+
}
252+
}
253+
}
254+
}
255+
}
157256

158257
module.exports = {
159258
milestoneAddedHandler,
160259
milestoneRemovedHandler,
161260
milestoneUpdatedHandler,
261+
milestoneUpdatedKafkaHandler,
162262
};

0 commit comments

Comments
 (0)