Skip to content

Commit 377b070

Browse files
committed
- When updating a milestone, set updatedBy to updated records.
- Handled scenario where the milestone's endDate was null and the startDate didn't change. Now the endDate gets updated. - When the last timeline's (ordered by order) endDate changes, the timeline gets set that endDate as its endDate. Added unit tests about this. - Updated Swagger documentation. - Removed startDate/endDate fields from Postman's milestone's PATCH requests, since now they are not accepted by the server.
1 parent 61cdff3 commit 377b070

File tree

4 files changed

+170
-16
lines changed

4 files changed

+170
-16
lines changed

postman.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3802,7 +3802,7 @@
38023802
],
38033803
"body": {
38043804
"mode": "raw",
3805-
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
3805+
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
38063806
},
38073807
"url": {
38083808
"raw": "{{api-url}}/v4/timelines/1/milestones/1",
@@ -3836,7 +3836,7 @@
38363836
],
38373837
"body": {
38383838
"mode": "raw",
3839-
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
3839+
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
38403840
},
38413841
"url": {
38423842
"raw": "{{api-url}}/v4/timelines/1/milestones/1",
@@ -3870,7 +3870,7 @@
38703870
],
38713871
"body": {
38723872
"mode": "raw",
3873-
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
3873+
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
38743874
},
38753875
"url": {
38763876
"raw": "{{api-url}}/v4/timelines/1/milestones/1",
@@ -3904,7 +3904,7 @@
39043904
],
39053905
"body": {
39063906
"mode": "raw",
3907-
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
3907+
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
39083908
},
39093909
"url": {
39103910
"raw": "{{api-url}}/v4/timelines/1/milestones/1",
@@ -3938,7 +3938,7 @@
39383938
],
39393939
"body": {
39403940
"mode": "raw",
3941-
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
3941+
"raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}"
39423942
},
39433943
"url": {
39443944
"raw": "{{api-url}}/v4/timelines/1/milestones/1",

src/routes/milestones/update.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ const permissions = tcMiddleware.permissions;
1717
/**
1818
* Cascades endDate/completionDate changes to all milestones with a greater order than the given one.
1919
* @param {Object} updatedMilestone the milestone that was updated
20-
* @returns {Promise<void>} a promise
20+
* @returns {Promise<void>} a promise that resolves to the last found milestone. If no milestone exists with an
21+
* order greater than the passed <b>updatedMilestone</b>, the promise will resolve to the passed
22+
* <b>updatedMilestone</b>
2123
*/
2224
function updateComingMilestones(updatedMilestone) {
2325
return models.Milestone.findAll({
@@ -32,16 +34,29 @@ function updateComingMilestones(updatedMilestone) {
3234
: updatedMilestone.endDate).add(1, 'days').toDate();
3335
const promises = _.map(comingMilestones, (_milestone) => {
3436
const milestone = _milestone;
35-
if (milestone.startDate.getTime() !== startDate.getTime()) {
37+
38+
// Update the milestone startDate if different than the iterated startDate
39+
if (!_.isEqual(milestone.startDate, startDate)) {
3640
milestone.startDate = startDate;
37-
milestone.endDate = moment.utc(startDate).add(milestone.duration - 1, 'days').toDate();
41+
milestone.updatedBy = updatedMilestone.updatedBy;
42+
}
43+
44+
// Calculate the endDate, and update it if different
45+
const endDate = moment.utc(startDate).add(milestone.duration - 1, 'days').toDate();
46+
if (!_.isEqual(milestone.endDate, endDate)) {
47+
milestone.endDate = endDate;
48+
milestone.updatedBy = updatedMilestone.updatedBy;
3849
}
50+
51+
// Set the next startDate value to the next day after completionDate if present or the endDate
3952
startDate = moment.utc(milestone.completionDate
4053
? milestone.completionDate
4154
: milestone.endDate).add(1, 'days').toDate();
4255
return milestone.save();
4356
});
44-
return Promise.all(promises);
57+
58+
// Resolve promise to the last updated milestone, or to the passed in updatedMilestone
59+
return Promise.all(promises).then(updatedMilestones => updatedMilestones.pop() || updatedMilestone);
4560
});
4661
}
4762

@@ -94,6 +109,8 @@ module.exports = [
94109
timelineId: req.params.timelineId,
95110
});
96111

112+
const timeline = req.timeline;
113+
97114
let original;
98115
let updated;
99116

@@ -173,7 +190,15 @@ module.exports = [
173190
.then(() => {
174191
// Update dates of the other milestones only if the completionDate or the duration changed
175192
if (!_.isEqual(original.completionDate, updated.completionDate) || original.duration !== updated.duration) {
176-
return updateComingMilestones(updated);
193+
return updateComingMilestones(updated)
194+
.then((lastTimelineMilestone) => {
195+
if (!_.isEqual(lastTimelineMilestone.endDate, timeline.endDate)) {
196+
timeline.endDate = lastTimelineMilestone.endDate;
197+
timeline.updatedBy = lastTimelineMilestone.updatedBy;
198+
return timeline.save();
199+
}
200+
return Promise.resolve();
201+
});
177202
}
178203
return Promise.resolve();
179204
}),

src/routes/milestones/update.spec.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,40 @@ describe('UPDATE Milestone', () => {
874874
});
875875
});
876876

877+
it('should return 200 for admin - changing completionDate will change the timeline\'s ' +
878+
// eslint-disable-next-line func-names
879+
'endDate', function (done) {
880+
this.timeout(10000);
881+
882+
request(server)
883+
.patch('/v4/timelines/1/milestones/2')
884+
.set({
885+
Authorization: `Bearer ${testUtil.jwts.admin}`,
886+
})
887+
.send({ param: _.assign({}, body.param, {
888+
completionDate: '2018-05-18T00:00:00.000Z', order: undefined, duration: undefined,
889+
}) })
890+
.expect(200)
891+
.end(() => {
892+
// Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z'
893+
// endDate: null to '2018-05-21T00:00:00.000Z'
894+
// Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z'
895+
// BELOW will be the new timeline's endDate
896+
// endDate: null to '2018-05-24T00:00:00.000Z'
897+
models.Timeline.findById(1)
898+
.then((timeline) => {
899+
// timeline start shouldn't change
900+
timeline.startDate.should.be.eql(new Date('2018-05-02T00:00:00.000Z'));
901+
902+
// timeline end should change
903+
timeline.endDate.should.be.eql(new Date('2018-05-24T00:00:00.000Z'));
904+
905+
done();
906+
})
907+
.catch(done);
908+
});
909+
});
910+
877911
it('should return 200 for admin - changing duration will cascade changes to coming ' +
878912
// eslint-disable-next-line func-names
879913
'milestones', function (done) {
@@ -906,6 +940,38 @@ describe('UPDATE Milestone', () => {
906940
});
907941
});
908942

943+
it('should return 200 for admin - changing duration will change the timeline\'s ' +
944+
// eslint-disable-next-line func-names
945+
'endDate', function (done) {
946+
this.timeout(10000);
947+
948+
request(server)
949+
.patch('/v4/timelines/1/milestones/2')
950+
.set({
951+
Authorization: `Bearer ${testUtil.jwts.admin}`,
952+
})
953+
.send({ param: _.assign({}, body.param, { duration: 5, order: undefined, completionDate: undefined }) })
954+
.expect(200)
955+
.end(() => {
956+
// Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z'
957+
// endDate: null to '2018-05-21T00:00:00.000Z'
958+
// Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z'
959+
// BELOW will be the new timeline's endDate
960+
// endDate: null to '2018-05-24T00:00:00.000Z'
961+
models.Timeline.findById(1)
962+
.then((timeline) => {
963+
// timeline start shouldn't change
964+
timeline.startDate.should.be.eql(new Date('2018-05-02T00:00:00.000Z'));
965+
966+
// timeline end should change
967+
timeline.endDate.should.be.eql(new Date('2018-05-24T00:00:00.000Z'));
968+
969+
done();
970+
})
971+
.catch(done);
972+
});
973+
});
974+
909975
it('should return 200 for connect admin', (done) => {
910976
request(server)
911977
.patch('/v4/timelines/1/milestones/1')

swagger.yaml

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,7 @@ paths:
13351335
name: body
13361336
required: true
13371337
schema:
1338-
$ref: '#/definitions/MilestoneBodyParam'
1338+
$ref: '#/definitions/MilestonePostBodyParam'
13391339
responses:
13401340
'403':
13411341
description: No permission or wrong token
@@ -1413,7 +1413,7 @@ paths:
14131413
in: body
14141414
required: true
14151415
schema:
1416-
$ref: "#/definitions/MilestoneBodyParam"
1416+
$ref: "#/definitions/MilestonePatchBodyParam"
14171417

14181418
delete:
14191419
tags:
@@ -3201,7 +3201,7 @@ definitions:
32013201
items:
32023202
$ref: "#/definitions/Timeline"
32033203

3204-
MilestoneRequest:
3204+
MilestonePostRequest:
32053205
title: Milestone request object
32063206
type: object
32073207
required:
@@ -3264,14 +3264,77 @@ definitions:
32643264
type: string
32653265
description: the milestone blocked text
32663266

3267-
MilestoneBodyParam:
3267+
MilestonePatchRequest:
3268+
title: Milestone request object
3269+
type: object
3270+
required:
3271+
- name
3272+
- duration
3273+
- status
3274+
- type
3275+
- order
3276+
- plannedText
3277+
- activeText
3278+
- completedText
3279+
- blockedText
3280+
properties:
3281+
name:
3282+
type: string
3283+
description: the milestone name
3284+
description:
3285+
type: string
3286+
description: the milestone description
3287+
duration:
3288+
type: number
3289+
format: integer
3290+
description: the milestone duration
3291+
completionDate:
3292+
type: string
3293+
format: date
3294+
description: the milestone completion date
3295+
status:
3296+
type: string
3297+
description: the milestone status
3298+
type:
3299+
type: string
3300+
description: the milestone type
3301+
details:
3302+
type: object
3303+
description: the milestone details
3304+
order:
3305+
type: number
3306+
format: integer
3307+
description: the milestone order
3308+
plannedText:
3309+
type: string
3310+
description: the milestone planned text
3311+
activeText:
3312+
type: string
3313+
description: the milestone active text
3314+
completedText:
3315+
type: string
3316+
description: the milestone completed text
3317+
blockedText:
3318+
type: string
3319+
description: the milestone blocked text
3320+
3321+
MilestonePostBodyParam:
3322+
title: Milestone body param
3323+
type: object
3324+
required:
3325+
- param
3326+
properties:
3327+
param:
3328+
$ref: "#/definitions/MilestonePostRequest"
3329+
3330+
MilestonePatchBodyParam:
32683331
title: Milestone body param
32693332
type: object
32703333
required:
32713334
- param
32723335
properties:
32733336
param:
3274-
$ref: "#/definitions/MilestoneRequest"
3337+
$ref: "#/definitions/MilestonePatchRequest"
32753338

32763339
Milestone:
32773340
title: Milestone object
@@ -3306,7 +3369,7 @@ definitions:
33063369
format: int64
33073370
description: READ-ONLY. User that last updated this object
33083371
readOnly: true
3309-
- $ref: "#/definitions/MilestoneRequest"
3372+
- $ref: "#/definitions/MilestonePostRequest"
33103373

33113374
MilestoneResponse:
33123375
title: Single milestone response object

0 commit comments

Comments
 (0)