Skip to content

Commit 2afb1a0

Browse files
committed
index ProjectPhases and PhaseProducts in ES when we use work and workitems endpoints for backward compatibility with phase endpoints
1 parent 84b3564 commit 2afb1a0

File tree

6 files changed

+222
-144
lines changed

6 files changed

+222
-144
lines changed

src/routes/workItems/create.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Joi from 'joi';
88

99
import models from '../../models';
1010
import util from '../../util';
11+
import { EVENT } from '../../constants';
1112

1213
const permissions = require('tc-core-library-js').middleware.permissions;
1314

@@ -120,6 +121,15 @@ module.exports = [
120121
});
121122
}))
122123
.then(() => {
124+
// Send events to buses
125+
req.log.debug('Sending event to RabbitMQ bus for phase product %d', newPhaseProduct.id);
126+
req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED,
127+
newPhaseProduct,
128+
{ correlationId: req.id },
129+
);
130+
req.log.debug('Sending event to Kafka bus for phase product %d', newPhaseProduct.id);
131+
req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, { req, created: newPhaseProduct });
132+
123133
res.status(201).json(util.wrapResponse(req.id, newPhaseProduct, 1, 201));
124134
})
125135
.catch((err) => { next(err); });

src/routes/workItems/delete.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import _ from 'lodash';
66
import Joi from 'joi';
77
import { middleware as tcMiddleware } from 'tc-core-library-js';
88
import models from '../../models';
9+
import { EVENT } from '../../constants';
910

1011
const permissions = tcMiddleware.permissions;
1112

@@ -65,15 +66,25 @@ module.exports = [
6566
.then((existing) => {
6667
if (!existing) {
6768
// handle 404
68-
const err = new Error('No active phase product found for project id ' +
69+
const err = new Error('No active work item found for project id ' +
6970
`${projectId}, phase id ${phaseId} and product id ${productId}`);
7071
err.status = 404;
7172
return Promise.reject(err);
7273
}
7374
return existing.update({ deletedBy: req.authUser.userId });
7475
})
7576
.then(entity => entity.destroy()))
76-
.then(() => {
77+
.then((deleted) => {
78+
req.log.debug('deleted work item', JSON.stringify(deleted, null, 2));
79+
80+
// Send events to buses
81+
req.app.services.pubsub.publish(
82+
EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED,
83+
deleted,
84+
{ correlationId: req.id },
85+
);
86+
req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, { req, deleted });
87+
7788
res.status(204).json({});
7889
})
7990
.catch(err => next(err));

src/routes/workItems/update.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Joi from 'joi';
77
import { middleware as tcMiddleware } from 'tc-core-library-js';
88
import models from '../../models';
99
import util from '../../util';
10+
import { EVENT } from '../../constants';
1011

1112
const permissions = tcMiddleware.permissions;
1213

@@ -47,6 +48,8 @@ module.exports = [
4748
const updatedProps = req.body.param;
4849
updatedProps.updatedBy = req.authUser.userId;
4950

51+
let previousValue;
52+
5053
models.sequelize.transaction(() => models.ProjectPhase.findOne({
5154
where: {
5255
id: phaseId,
@@ -88,11 +91,24 @@ module.exports = [
8891
throw err;
8992
}
9093

94+
previousValue = _.clone(existing.get({ plain: true }));
9195
_.extend(existing, updatedProps);
9296
return existing.save().catch(next);
9397
}))
9498
.then((updated) => {
9599
req.log.debug('updated work item', JSON.stringify(updated, null, 2));
100+
101+
const updatedValue = updated.get({ plain: true });
102+
103+
// emit original and updated project phase information
104+
req.app.services.pubsub.publish(
105+
EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED,
106+
{ original: previousValue, updated: updatedValue },
107+
{ correlationId: req.id },
108+
);
109+
req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED,
110+
{ req, original: previousValue, updated: updatedValue });
111+
96112
res.json(util.wrapResponse(req.id, updated));
97113
}).catch(err => next(err));
98114
},

src/routes/works/create.js

Lines changed: 135 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,151 @@
11
/**
22
* API to add a phase as work
33
*/
4-
import validate from 'express-validation';
5-
import _ from 'lodash';
6-
import Joi from 'joi';
7-
import Sequelize from 'sequelize';
4+
import validate from 'express-validation';
5+
import _ from 'lodash';
6+
import Joi from 'joi';
7+
import Sequelize from 'sequelize';
88

9-
import models from '../../models';
10-
import util from '../../util';
9+
import models from '../../models';
10+
import util from '../../util';
11+
import { EVENT } from '../../constants';
1112

12-
const permissions = require('tc-core-library-js').middleware.permissions;
13+
const permissions = require('tc-core-library-js').middleware.permissions;
1314

14-
const schema = {
15-
params: {
16-
projectId: Joi.number().integer().positive().required(),
17-
workStreamId: Joi.number().integer().positive().required(),
18-
},
19-
body: {
20-
param: Joi.object().keys({
21-
name: Joi.string().required(),
22-
status: Joi.string().required(),
23-
startDate: Joi.date().optional(),
24-
endDate: Joi.date().optional(),
25-
duration: Joi.number().min(0).optional(),
26-
budget: Joi.number().min(0).optional(),
27-
spentBudget: Joi.number().min(0).optional(),
28-
progress: Joi.number().min(0).optional(),
29-
details: Joi.any().optional(),
30-
order: Joi.number().integer().optional(),
31-
productTemplateId: Joi.number().integer().positive().optional(),
32-
}).required(),
33-
},
34-
};
15+
const schema = {
16+
params: {
17+
projectId: Joi.number().integer().positive().required(),
18+
workStreamId: Joi.number().integer().positive().required(),
19+
},
20+
body: {
21+
param: Joi.object().keys({
22+
name: Joi.string().required(),
23+
status: Joi.string().required(),
24+
startDate: Joi.date().optional(),
25+
endDate: Joi.date().optional(),
26+
duration: Joi.number().min(0).optional(),
27+
budget: Joi.number().min(0).optional(),
28+
spentBudget: Joi.number().min(0).optional(),
29+
progress: Joi.number().min(0).optional(),
30+
details: Joi.any().optional(),
31+
order: Joi.number().integer().optional(),
32+
productTemplateId: Joi.number().integer().positive().optional(),
33+
}).required(),
34+
},
35+
};
3536

36-
module.exports = [
37-
// validate request payload
38-
validate(schema),
39-
// check permission
40-
permissions('work.create'),
41-
// do the real work
42-
(req, res, next) => {
43-
// default values
44-
const projectId = _.parseInt(req.params.projectId);
45-
const workStreamId = _.parseInt(req.params.workStreamId);
37+
module.exports = [
38+
// validate request payload
39+
validate(schema),
40+
// check permission
41+
permissions('work.create'),
42+
// do the real work
43+
(req, res, next) => {
44+
// default values
45+
const projectId = _.parseInt(req.params.projectId);
46+
const workStreamId = _.parseInt(req.params.workStreamId);
4647

47-
const data = req.body.param;
48-
_.assign(data, {
49-
projectId,
50-
createdBy: req.authUser.userId,
51-
updatedBy: req.authUser.userId,
52-
});
48+
const data = req.body.param;
49+
_.assign(data, {
50+
projectId,
51+
createdBy: req.authUser.userId,
52+
updatedBy: req.authUser.userId,
53+
});
5354

54-
let existingWorkStream = null;
55-
let newProjectPhase = null;
55+
let existingWorkStream = null;
56+
let newProjectPhase = null;
5657

57-
req.log.debug('Create Work - Starting transaction');
58-
models.sequelize.transaction(() => models.WorkStream.findOne({
59-
where: {
60-
id: workStreamId,
61-
projectId,
62-
deletedAt: { $eq: null },
63-
},
64-
})
65-
.then((_existingWorkStream) => {
66-
if (!_existingWorkStream) {
67-
// handle 404
68-
const err = new Error(`active work stream not found for project id ${projectId} ` +
69-
`and work stream id ${workStreamId}`);
70-
err.status = 404;
71-
throw err;
72-
}
58+
req.log.debug('Create Work - Starting transaction');
59+
models.sequelize.transaction(() => models.WorkStream.findOne({
60+
where: {
61+
id: workStreamId,
62+
projectId,
63+
deletedAt: { $eq: null },
64+
},
65+
})
66+
.then((_existingWorkStream) => {
67+
if (!_existingWorkStream) {
68+
// handle 404
69+
const err = new Error(`active work stream not found for project id ${projectId} ` +
70+
`and work stream id ${workStreamId}`);
71+
err.status = 404;
72+
throw err;
73+
}
7374

74-
existingWorkStream = _existingWorkStream;
75+
existingWorkStream = _existingWorkStream;
7576

76-
if (data.startDate !== null && data.endDate !== null && data.startDate > data.endDate) {
77-
const err = new Error('startDate must not be after endDate.');
78-
err.status = 422;
79-
throw err;
80-
}
81-
return models.ProjectPhase.create(data);
82-
})
83-
.then((_newProjectPhase) => {
84-
newProjectPhase = _.omit(_newProjectPhase, ['deletedAt', 'deletedBy']);
85-
return existingWorkStream.addProjectPhase(_newProjectPhase.id);
77+
if (data.startDate !== null && data.endDate !== null && data.startDate > data.endDate) {
78+
const err = new Error('startDate must not be after endDate.');
79+
err.status = 422;
80+
throw err;
81+
}
82+
return models.ProjectPhase.create(data);
8683
})
87-
.then(() => {
88-
req.log.debug('re-ordering the other phases');
84+
.then((_newProjectPhase) => {
85+
newProjectPhase = _.omit(_newProjectPhase, ['deletedAt', 'deletedBy']);
86+
return existingWorkStream.addProjectPhase(_newProjectPhase.id);
87+
})
88+
.then(() => {
89+
req.log.debug('re-ordering the other phases');
8990

90-
if (_.isNil(newProjectPhase.order)) {
91-
return Promise.resolve();
92-
}
93-
// Increase the order of the other phases in the same project,
94-
// which have `order` >= this phase order
95-
return models.ProjectPhase.update({ order: Sequelize.literal('"order" + 1') }, {
96-
where: {
97-
projectId,
98-
id: { $ne: newProjectPhase.id },
99-
order: { $gte: newProjectPhase.order },
100-
},
101-
});
102-
})
103-
.then(() => {
104-
if (_.isNil(data.productTemplateId)) {
105-
return Promise.resolve();
106-
}
91+
if (_.isNil(newProjectPhase.order)) {
92+
return Promise.resolve();
93+
}
94+
// Increase the order of the other phases in the same project,
95+
// which have `order` >= this phase order
96+
return models.ProjectPhase.update({ order: Sequelize.literal('"order" + 1') }, {
97+
where: {
98+
projectId,
99+
id: { $ne: newProjectPhase.id },
100+
order: { $gte: newProjectPhase.order },
101+
},
102+
});
103+
})
104+
.then(() => {
105+
if (_.isNil(data.productTemplateId)) {
106+
return Promise.resolve();
107+
}
108+
109+
// Get the product template
110+
return models.ProductTemplate.findById(data.productTemplateId)
111+
.then((productTemplate) => {
112+
if (!productTemplate) {
113+
const err = new Error(`Product template does not exist with id = ${data.productTemplateId}`);
114+
err.status = 422;
115+
throw err;
116+
}
107117

108-
// Get the product template
109-
return models.ProductTemplate.findById(data.productTemplateId)
110-
.then((productTemplate) => {
111-
if (!productTemplate) {
112-
const err = new Error(`Product template does not exist with id = ${data.productTemplateId}`);
113-
err.status = 422;
114-
throw err;
115-
}
118+
// Create the phase product
119+
return models.PhaseProduct.create({
120+
name: productTemplate.name,
121+
templateId: data.productTemplateId,
122+
type: productTemplate.productKey,
123+
projectId,
124+
phaseId: newProjectPhase.id,
125+
createdBy: req.authUser.userId,
126+
updatedBy: req.authUser.userId,
127+
})
128+
.then((phaseProduct) => {
129+
newProjectPhase.products = [
130+
_.omit(phaseProduct.toJSON(), ['deletedAt', 'deletedBy']),
131+
];
132+
});
133+
});
134+
}))
135+
.then(() => {
136+
// Send events to buses
137+
req.log.debug('Sending event to RabbitMQ bus for project phase %d', newProjectPhase.id);
138+
req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED,
139+
newProjectPhase,
140+
{ correlationId: req.id },
141+
);
142+
req.log.debug('Sending event to Kafka bus for project phase %d', newProjectPhase.id);
143+
req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, { req, created: newProjectPhase });
116144

117-
// Create the phase product
118-
return models.PhaseProduct.create({
119-
name: productTemplate.name,
120-
templateId: data.productTemplateId,
121-
type: productTemplate.productKey,
122-
projectId,
123-
phaseId: newProjectPhase.id,
124-
createdBy: req.authUser.userId,
125-
updatedBy: req.authUser.userId,
126-
})
127-
.then((phaseProduct) => {
128-
newProjectPhase.products = [
129-
_.omit(phaseProduct.toJSON(), ['deletedAt', 'deletedBy']),
130-
];
131-
});
132-
});
133-
}))
134-
.then(() => {
135-
res.status(201).json(util.wrapResponse(req.id, newProjectPhase));
136-
})
137-
.catch(next);
138-
},
139-
];
145+
res.status(201).json(util.wrapResponse(req.id, newProjectPhase, 1, 201));
146+
})
147+
.catch((err) => {
148+
util.handleError('Error creating work', err, req, next);
149+
});
150+
},
151+
];

0 commit comments

Comments
 (0)