Skip to content

Commit 3ba8c3b

Browse files
author
vikasrohit
authored
Merge pull request #369 from topcoder-platform/feature/topics-for-works
Feature/topics for works
2 parents 6fbebfb + 214f2d3 commit 3ba8c3b

File tree

20 files changed

+870
-178
lines changed

20 files changed

+870
-178
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ jobs:
4848
- POSTGRES_USER: circle_test
4949
- POSTGRES_DB: circle_test
5050
- image: elasticsearch:2.3
51+
- image: rabbitmq:3-management
5152
environment:
5253
DB_MASTER_URL: postgres://circle_test:@127.0.0.1:5432/circle_test
5354
AUTH_SECRET: secret

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"mocha": true
99
},
1010
"rules": {
11-
"import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/*.spec.js", "**/serviceMocks.js"]}],
11+
"import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/*.spec.js", "src/tests/*.js"]}],
1212
"max-len": ["error", { "ignoreComments": true, "code": 120 }],
1313
"valid-jsdoc": ["error", {
1414
"requireReturn": true,

package-lock.json

Lines changed: 21 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/events/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export const rabbitHandlers = {
4444
[EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED]: phaseProductAddedHandler,
4545
[EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED]: phaseProductRemovedHandler,
4646
[EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED]: phaseProductUpdatedHandler,
47-
4847
// Timeline and milestone
4948
'timeline.initial': timelineAddedHandler,
5049
[EVENT.ROUTING_KEY.TIMELINE_ADDED]: timelineAddedHandler,

src/events/projectPhases/index.js

Lines changed: 124 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,48 @@ import config from 'config';
77
import _ from 'lodash';
88
import Promise from 'bluebird';
99
import util from '../../util';
10+
import { TIMELINE_REFERENCES } from '../../constants';
11+
1012
import messageService from '../../services/messageService';
1113

1214
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
1315
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
1416

1517
const eClient = util.getElasticSearchClient();
1618

19+
/**
20+
* Build topics data based on route parameter.
21+
*
22+
* @param {Object} logger logger to log along with trace id
23+
* @param {Object} phase phase object
24+
* @param {String} route route value can be PHASE/WORK
25+
* @returns {undefined}
26+
*/
27+
const buildTopicsData = (logger, phase, route) => {
28+
if (route === TIMELINE_REFERENCES.WORK) {
29+
return [{
30+
tag: `work#${phase.id}-details`,
31+
title: `${phase.name} - Details`,
32+
reference: 'project',
33+
referenceId: `${phase.projectId}`,
34+
body: 'This is the beginning of your phase discussion. During execution of this phase, all related communication will be conducted here - phase updates, questions and answers, suggestions, etc. If you haven\'t already, do please take a moment to review the form in the Specification tab above and fill in as much detail as possible. This will help get started faster. Thanks!', // eslint-disable-line
35+
}, {
36+
tag: `work#${phase.id}-requirements`,
37+
title: `${phase.name} - Requirements`,
38+
reference: 'project',
39+
referenceId: `${phase.projectId}`,
40+
body: 'This is the beginning of your phase discussion. During execution of this phase, all related communication will be conducted here - phase updates, questions and answers, suggestions, etc. If you haven\'t already, do please take a moment to review the form in the Specification tab above and fill in as much detail as possible. This will help get started faster. Thanks!', // eslint-disable-line
41+
}];
42+
}
43+
return [{
44+
tag: `phase#${phase.id}`,
45+
title: phase.name,
46+
reference: 'project',
47+
referenceId: `${phase.projectId}`,
48+
body: 'This is the beginning of your phase discussion. During execution of this phase, all related communication will be conducted here - phase updates, questions and answers, suggestions, etc. If you haven\'t already, do please take a moment to review the form in the Specification tab above and fill in as much detail as possible. This will help get started faster. Thanks!', // eslint-disable-line
49+
}];
50+
};
51+
1752
/**
1853
* Indexes the project phase in the elastic search.
1954
*
@@ -59,26 +94,22 @@ const indexProjectPhase = Promise.coroutine(function* (logger, phase) { // eslin
5994
});
6095

6196
/**
62-
* Creates a new phase topic in message api.
97+
* Creates topics in message api
6398
*
6499
* @param {Object} logger logger to log along with trace id
65-
* @param {Object} msg event payload
100+
* @param {Object} phase phase object
101+
* @param {String} route route value can be `phase`/`work`
66102
* @returns {undefined}
67103
*/
68-
const createPhaseTopic = Promise.coroutine(function* (logger, phase) { // eslint-disable-line func-names
104+
const createTopics = Promise.coroutine(function* (logger, phase, route) { // eslint-disable-line func-names
69105
try {
70-
logger.debug('Creating topic for phase with phase', phase);
71-
const topic = yield messageService.createTopic({
72-
reference: 'project',
73-
referenceId: `${phase.projectId}`,
74-
tag: `phase#${phase.id}`,
75-
title: phase.name,
76-
body: 'This is the beginning of your phase discussion. During execution of this phase, all related communication will be conducted here - phase updates, questions and answers, suggestions, etc. If you haven\'t already, do please take a moment to review the form in the Specification tab above and fill in as much detail as possible. This will help get started faster. Thanks!', // eslint-disable-line
77-
}, logger);
78-
logger.debug('topic for the phase created successfully');
79-
logger.debug('created topic', topic);
106+
logger.debug(`Creating topics for ${route} with phase`, phase);
107+
const topicsData = buildTopicsData(logger, phase, route);
108+
const topics = yield Promise.all(_.map(topicsData, topicData => messageService.createTopic(topicData, logger)));
109+
logger.debug(`topics for the ${route} created successfully`);
110+
logger.debug('created topics', topics);
80111
} catch (error) {
81-
logger.error('Error in creating topic for the project phase', error);
112+
logger.error(`Error in creating topic for ${route}`, error);
82113
// don't throw the error back to nack the bus, because we don't want to get multiple topics per phase
83114
// we can create topic for a phase manually, if somehow it fails
84115
}
@@ -92,12 +123,14 @@ const createPhaseTopic = Promise.coroutine(function* (logger, phase) { // eslint
92123
* @returns {undefined}
93124
*/
94125
const projectPhaseAddedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names
95-
const phase = JSON.parse(msg.content.toString());
126+
const data = JSON.parse(msg.content.toString());
127+
const phase = _.get(data, 'added', {});
128+
const route = _.get(data, 'route', 'PHASE');
96129
try {
97130
logger.debug('calling indexProjectPhase', phase);
98131
yield indexProjectPhase(logger, phase, channel);
99132
logger.debug('calling createPhaseTopic', phase);
100-
yield createPhaseTopic(logger, phase);
133+
yield createTopics(logger, phase, route);
101134
channel.ack(msg);
102135
} catch (error) {
103136
logger.error('Error handling project.phase.added event', error);
@@ -135,31 +168,46 @@ const updateIndexProjectPhase = Promise.coroutine(function* (logger, data) { //
135168
});
136169

137170
/**
138-
* Creates a new phase topic in message api.
171+
* Update one topic
172+
*
173+
* @param {Object} logger logger to log along with trace id
174+
* @param {Object} phase phase object
175+
* @param {Object} topicUpdate updated topic data
176+
* @returns {undefined}
177+
*/
178+
const updateOneTopic = Promise.coroutine(function* (logger, phase, topicUpdate) { // eslint-disable-line func-names
179+
const topic = yield messageService.getTopicByTag(phase.projectId, topicUpdate.tag, logger);
180+
logger.trace('Topic', topic);
181+
const title = topicUpdate.title;
182+
const titleChanged = topic && topic.title !== title;
183+
logger.trace('titleChanged', titleChanged);
184+
const contentPost = topic && topic.posts && topic.posts.length > 0 ? topic.posts[0] : null;
185+
logger.trace('contentPost', contentPost);
186+
const postId = _.get(contentPost, 'id');
187+
const content = _.get(contentPost, 'body');
188+
if (postId && content && titleChanged) {
189+
const updatedTopic = yield messageService.updateTopic(topic.id, { title, postId, content }, logger);
190+
logger.debug('topic updated successfully');
191+
logger.trace('updated topic', updatedTopic);
192+
}
193+
});
194+
195+
/**
196+
* Update topics in message api.
139197
*
140198
* @param {Object} logger logger to log along with trace id
141-
* @param {Object} msg event payload
199+
* @param {Object} phase phase object
200+
* @param {String} route route value can be `phase`/`work`
142201
* @returns {undefined}
143202
*/
144-
const updatePhaseTopic = Promise.coroutine(function* (logger, phase) { // eslint-disable-line func-names
203+
const updateTopics = Promise.coroutine(function* (logger, phase, route) { // eslint-disable-line func-names
145204
try {
146-
logger.debug('Updating topic for phase with phase', phase);
147-
const topic = yield messageService.getPhaseTopic(phase.projectId, phase.id, logger);
148-
logger.trace('Topic', topic);
149-
const title = phase.name;
150-
const titleChanged = topic && topic.title !== title;
151-
logger.trace('titleChanged', titleChanged);
152-
const contentPost = topic && topic.posts && topic.posts.length > 0 ? topic.posts[0] : null;
153-
logger.trace('contentPost', contentPost);
154-
const postId = _.get(contentPost, 'id');
155-
const content = _.get(contentPost, 'body');
156-
if (postId && content && titleChanged) {
157-
const updatedTopic = yield messageService.updateTopic(topic.id, { title, postId, content }, logger);
158-
logger.debug('topic for the phase updated successfully');
159-
logger.trace('updated topic', updatedTopic);
160-
}
205+
logger.debug(`Updating topic for ${route} with phase`, phase);
206+
const topicsData = buildTopicsData(logger, phase, route);
207+
yield Promise.all(_.map(topicsData, topicData => updateOneTopic(logger, phase, topicData)));
208+
logger.debug(`topics for the ${route} updated successfully`);
161209
} catch (error) {
162-
logger.error('Error in updating topic for the project phase', error);
210+
logger.error(`Error in updating topic for ${route}`, error);
163211
// don't throw the error back to nack the bus, because we don't want to get multiple topics per phase
164212
// we can create topic for a phase manually, if somehow it fails
165213
}
@@ -175,10 +223,11 @@ const updatePhaseTopic = Promise.coroutine(function* (logger, phase) { // eslint
175223
const projectPhaseUpdatedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names
176224
try {
177225
const data = JSON.parse(msg.content.toString());
226+
const route = _.get(data, 'route', 'PHASE');
178227
logger.debug('calling updateIndexProjectPhase', data);
179228
yield updateIndexProjectPhase(logger, data, channel);
180-
logger.debug('calling updatePhaseTopic', data.updated);
181-
yield updatePhaseTopic(logger, data.updated);
229+
logger.debug('calling updateTopics', data.updated);
230+
yield updateTopics(logger, data.updated, route);
182231
channel.ack(msg);
183232
} catch (error) {
184233
logger.error('Error handling project.phase.updated event', error);
@@ -197,13 +246,14 @@ const projectPhaseUpdatedHandler = Promise.coroutine(function* (logger, msg, cha
197246
const removePhaseFromIndex = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names
198247
try {
199248
const data = JSON.parse(msg.content.toString());
200-
const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: data.projectId });
201-
const phases = _.filter(doc._source.phases, single => single.id !== data.id); // eslint-disable-line no-underscore-dangle
249+
const phase = _.get(data, 'deleted', {});
250+
const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: phase.projectId });
251+
const phases = _.filter(doc._source.phases, single => single.id !== phase.id); // eslint-disable-line no-underscore-dangle
202252
const merged = _.assign(doc._source, { phases }); // eslint-disable-line no-underscore-dangle
203253
yield eClient.update({
204254
index: ES_PROJECT_INDEX,
205255
type: ES_PROJECT_TYPE,
206-
id: data.projectId,
256+
id: phase.projectId,
207257
body: {
208258
doc: merged,
209259
},
@@ -217,26 +267,46 @@ const removePhaseFromIndex = Promise.coroutine(function* (logger, msg) { // esli
217267
});
218268

219269
/**
220-
* Removes the phase topic from the message api.
270+
* Removes one topic from the message api.
221271
*
222-
* @param {Object} logger logger to log along with trace id
223-
* @param {Object} msg event payload
272+
* @param {Object} logger logger to log along with trace id
273+
* @param {Object} phase phase object
274+
* @param {Object} tag topic tag
224275
* @returns {undefined}
225276
*/
226-
const removePhaseTopic = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names
277+
const removeOneTopic = Promise.coroutine(function* (logger, phase, tag) { // eslint-disable-line func-names
227278
try {
228-
const phase = JSON.parse(msg.content.toString());
229-
const phaseTopic = yield messageService.getPhaseTopic(phase.projectId, phase.id, logger);
279+
const phaseTopic = yield messageService.getTopicByTag(phase.projectId, tag, logger);
230280
yield messageService.deletePosts(phaseTopic.id, phaseTopic.postIds, logger);
231281
yield messageService.deleteTopic(phaseTopic.id, logger);
232-
logger.debug('topic for the phase removed successfully');
233282
} catch (error) {
234-
logger.error('Error in removing topic for the project phase', error);
283+
logger.error(`Error removing topic by tab ${tag}`, error);
235284
// don't throw the error back to nack the bus
236285
// we can delete topic for a phase manually, if somehow it fails
237286
}
238287
});
239288

289+
/**
290+
* Remove topics in message api.
291+
*
292+
* @param {Object} logger logger to log along with trace id
293+
* @param {Object} phase phase object
294+
* @param {String} route route value can be `phase`/`work`
295+
* @returns {undefined}
296+
*/
297+
const removeTopics = Promise.coroutine(function* (logger, phase, route) { // eslint-disable-line func-names
298+
try {
299+
logger.debug(`Removing topic for ${route} with phase`, phase);
300+
const topicsData = buildTopicsData(logger, phase, route);
301+
yield Promise.all(_.map(topicsData, topicData => removeOneTopic(logger, phase, topicData.tag)));
302+
logger.debug(`topics for the ${route} removed successfully`);
303+
} catch (error) {
304+
logger.error(`Error in removing topic for ${route}`, error);
305+
// don't throw the error back to nack the bus, because we don't want to get multiple topics per phase
306+
// we can create topic for a phase manually, if somehow it fails
307+
}
308+
});
309+
240310
/**
241311
* Handler for project phase deleted event
242312
* @param {Object} logger logger to log along with trace id
@@ -247,7 +317,11 @@ const removePhaseTopic = Promise.coroutine(function* (logger, msg) { // eslint-d
247317
const projectPhaseRemovedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names
248318
try {
249319
yield removePhaseFromIndex(logger, msg, channel);
250-
yield removePhaseTopic(logger, msg);
320+
const data = JSON.parse(msg.content.toString());
321+
const phase = _.get(data, 'deleted', {});
322+
const route = _.get(data, 'route');
323+
logger.debug('calling removeTopics');
324+
yield removeTopics(logger, phase, route);
251325
channel.ack(msg);
252326
} catch (error) {
253327
logger.error('Error fetching project document from elasticsearch', error);
@@ -261,5 +335,5 @@ module.exports = {
261335
projectPhaseAddedHandler,
262336
projectPhaseRemovedHandler,
263337
projectPhaseUpdatedHandler,
264-
createPhaseTopic,
338+
createPhaseTopic: createTopics,
265339
};

src/routes/phases/create.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Sequelize from 'sequelize';
55

66
import models from '../../models';
77
import util from '../../util';
8-
import { EVENT } from '../../constants';
8+
import { EVENT, TIMELINE_REFERENCES } from '../../constants';
99

1010
const permissions = require('tc-core-library-js').middleware.permissions;
1111

@@ -127,7 +127,7 @@ module.exports = [
127127
// Send events to buses
128128
req.log.debug('Sending event to RabbitMQ bus for project phase %d', newProjectPhase.id);
129129
req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED,
130-
newProjectPhase,
130+
{ added: newProjectPhase, route: TIMELINE_REFERENCES.PHASE },
131131
{ correlationId: req.id },
132132
);
133133
req.log.debug('Sending event to Kafka bus for project phase %d', newProjectPhase.id);

0 commit comments

Comments
 (0)