Skip to content

Commit fcee870

Browse files
authored
Merge pull request #4215 from yoution/feature/generic-phases
Generic Phases
2 parents e8bf3b2 + 64e8369 commit fcee870

File tree

19 files changed

+1085
-437
lines changed

19 files changed

+1085
-437
lines changed

src/components/Layout/Layout.scss

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,5 @@
2222
&[data-route*='new-project'] {
2323
background-color: $tc-gray-neutral-light;
2424
}
25-
&[data-route*='add-phase'] {
26-
background-color: $tc-gray-neutral-light;
27-
}
2825
}
2926
}

src/components/TopBar/TopBarContainer.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,6 @@ class TopBarContainer extends React.Component {
4949

5050
render() {
5151

52-
const location = this.props.location.pathname
53-
if (location && (location.substr(location.lastIndexOf('/') + 1) === 'add-phase')) {
54-
return (
55-
<div />
56-
)
57-
}
5852
const { user, toolbar, userRoles } = this.props
5953
const userHandle = _.get(user, 'handle')
6054
const bigPhotoURL = _.get(user, 'photoURL')

src/config/constants.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ export const SET_PROJECTS_INFINITE_AUTOLOAD = 'SET_PROJECTS_INFINITE_AUTOLOAD'
8585
export const SET_PROJECTS_LIST_VIEW = 'SET_PROJECTS_LIST_VIEW'
8686

8787

88+
// project phases and timeline and milestones
89+
export const CREATE_PROJECT_PHASE_TIMELINE_MILESTONES = 'CREATE_PROJECT_PHASE_TIMELINE_MILESTONES'
90+
export const CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_FAILURE = 'CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_FAILURE'
91+
export const CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS = 'CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_SUCCESS'
92+
export const CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_PENDING = 'CREATE_PROJECT_PHASE_TIMELINE_MILESTONES_PENDING'
93+
94+
8895
// Delete project
8996
export const DELETE_PROJECT = 'DELETE_PROJECT'
9097
export const DELETE_PROJECT_PENDING = 'DELETE_PROJECT_PENDING'
@@ -892,6 +899,7 @@ export const POSTS_BUNDLE_TIME_DIFF = 1000 * 60 * 10 // 10 min difference
892899
export const MILESTONE_STATUS = {
893900
UNPLANNED: 'in_review',
894901
PLANNED: 'reviewed',
902+
DRAFT: 'draft',
895903
ACTIVE: 'active',
896904
BLOCKED: 'paused',
897905
COMPLETED: 'completed',

src/helpers/milestoneHelper.js

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ function mergeJsonObjects(targetObj, sourceObj) {
5555

5656
function updateMilestone(milestone, updatedProps) {
5757
const entityToUpdate = updatedProps
58-
const durationChanged = entityToUpdate.duration && entityToUpdate.duration !== milestone.duration
58+
5959
const statusChanged = entityToUpdate.status && entityToUpdate.status !== milestone.status
6060
const completionDateChanged = entityToUpdate.completionDate
6161
&& !_.isEqual(milestone.completionDate, entityToUpdate.completionDate)
@@ -64,40 +64,23 @@ function updateMilestone(milestone, updatedProps) {
6464
// Merge JSON fields
6565
entityToUpdate.details = mergeJsonObjects(milestone.details, entityToUpdate.details)
6666

67-
let actualStartDateCanged = false
6867
// if status has changed
6968
if (statusChanged) {
7069
// if status has changed to be completed, set the compeltionDate if not provided
7170
if (entityToUpdate.status === MILESTONE_STATUS.COMPLETED) {
7271
entityToUpdate.completionDate = entityToUpdate.completionDate ? entityToUpdate.completionDate : today.toISOString()
73-
entityToUpdate.duration = moment.utc(entityToUpdate.completionDate)
74-
.diff(entityToUpdate.actualStartDate, 'days') + 1
7572
}
7673
// if status has changed to be active, set the startDate to today
7774
if (entityToUpdate.status === MILESTONE_STATUS.ACTIVE) {
7875
// NOTE: not updating startDate as activating a milestone should not update the scheduled start date
7976
// entityToUpdate.startDate = today
8077
// should update actual start date
8178
entityToUpdate.actualStartDate = today.toISOString()
82-
actualStartDateCanged = true
8379
}
8480
}
8581

86-
// Updates the end date of the milestone if:
87-
// 1. if duration of the milestone is udpated, update its end date
88-
// OR
89-
// 2. if actual start date is updated, updating the end date of the activated milestone because
90-
// early or late start of milestone, we are essentially changing the end schedule of the milestone
91-
if (durationChanged || actualStartDateCanged) {
92-
const updatedStartDate = actualStartDateCanged ? entityToUpdate.actualStartDate : milestone.startDate
93-
const updatedDuration = _.get(entityToUpdate, 'duration', milestone.duration)
94-
entityToUpdate.endDate = moment.utc(updatedStartDate).add(updatedDuration - 1, 'days').toDate().toISOString()
95-
}
96-
9782
// if completionDate has changed
9883
if (!statusChanged && completionDateChanged) {
99-
entityToUpdate.duration = moment.utc(entityToUpdate.completionDate)
100-
.diff(entityToUpdate.actualStartDate, 'days') + 1
10184
entityToUpdate.status = MILESTONE_STATUS.COMPLETED
10285
}
10386

@@ -141,7 +124,7 @@ function updateComingMilestones(origMilestone, updMilestone, timelineMilestones)
141124
}
142125

143126
// Calculate the endDate, and update it if different
144-
const endDate = moment.utc(updateProps.startDate || milestone.startDate).add(milestone.duration - 1, 'days').toDate().toISOString()
127+
const endDate = moment.utc(updateProps.endDate || milestone.endDate).toDate().toISOString()
145128
if (!_.isEqual(milestone.endDate, endDate)) {
146129
updateProps.endDate = endDate
147130
updateProps.updatedBy = updMilestone.updatedBy
@@ -176,7 +159,6 @@ function cascadeMilestones(originalMilestone, updatedMilestone, timelineMileston
176159

177160
// we need to recalculate change in fields because we update some fields before making actual update
178161
const needToCascade = !_.isEqual(original.completionDate, updated.completionDate) // completion date changed
179-
|| original.duration !== updated.duration // duration changed
180162
|| original.actualStartDate !== updated.actualStartDate // actual start date updated
181163

182164
if (needToCascade) {
@@ -199,3 +181,8 @@ export const processUpdateMilestone = (milestone, updatedProps, timelineMileston
199181

200182
return { updatedMilestone, updatedTimelineMilestones }
201183
}
184+
185+
186+
export const processDeleteMilestone = (index, timelineMilestones) => {
187+
return update(timelineMilestones, {$splice:[ [index, 1]]})
188+
}

src/projects/actions/productsTimelines.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ import {
2828
PHASE_STATUS_COMPLETED,
2929
BULK_UPDATE_PRODUCT_MILESTONES,
3030
} from '../../config/constants'
31-
import { processUpdateMilestone } from '../../helpers/milestoneHelper'
31+
import { processUpdateMilestone,
32+
processDeleteMilestone
33+
} from '../../helpers/milestoneHelper'
3234

3335
/**
3436
* Get the next milestone in the list, which is not hidden
@@ -110,7 +112,12 @@ export function updateProductMilestone(productId, timelineId, milestoneId, updat
110112
const timeline = getState().productsTimelines[productId].timeline
111113
const milestoneIdx = _.findIndex(timeline.milestones, { id: milestoneId })
112114
const milestone = timeline.milestones[milestoneIdx]
113-
const updatedTimelineMilestones = processUpdateMilestone(milestone, updatedProps, timeline.milestones).updatedTimelineMilestones
115+
let updatedTimelineMilestones
116+
if (!updatedProps) {
117+
updatedTimelineMilestones = processDeleteMilestone(milestoneIdx, timeline.milestones)
118+
} else {
119+
updatedTimelineMilestones = processUpdateMilestone(milestone, updatedProps, timeline.milestones).updatedTimelineMilestones
120+
}
114121

115122
dispatch({
116123
type: UPDATE_PRODUCT_MILESTONE_PENDING,

src/projects/actions/project.js

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
getProjectMemberInvites,
2020
} from '../../api/projectMemberInvites'
2121
import {
22+
updateMilestones,
2223
createTimeline,
2324
} from '../../api/timelines'
2425
import {
@@ -65,6 +66,7 @@ import {
6566
PHASE_STATUS_DRAFT,
6667
LOAD_PROJECT_MEMBERS,
6768
LOAD_PROJECT_MEMBER_INVITES,
69+
CREATE_PROJECT_PHASE_TIMELINE_MILESTONES,
6870
LOAD_PROJECT_MEMBER
6971
} from '../../config/constants'
7072
import {
@@ -328,6 +330,41 @@ export function createProjectPhaseAndProduct(project, projectTemplate, status =
328330
})
329331
}
330332

333+
334+
/**
335+
* Create phase and product and milestones for the project
336+
*
337+
* @param {Object} project project
338+
* @param {Object} projectTemplate project template
339+
* @param {String} status (optional) project/phase status
340+
* @param {Object} startDate phase startDate
341+
* @param {Object} endDate phase endDate
342+
* @param {Array} milestones milestones
343+
*
344+
* @return {Promise} project
345+
*/
346+
function createPhaseAndMilestonesRequest(project, projectTemplate, status = PHASE_STATUS_DRAFT, startDate, endDate, milestones) {
347+
return createProjectPhaseAndProduct(project, projectTemplate, status, startDate, endDate).then(({timeline, phase, project, product}) => {
348+
return updateMilestones(timeline.id, milestones).then((data) => ({
349+
phase,
350+
project,
351+
product,
352+
timeline,
353+
milestones: data
354+
}))
355+
})
356+
}
357+
358+
359+
export function createPhaseAndMilestones(project, projectTemplate, status, startDate, endDate, milestones) {
360+
return (dispatch) => {
361+
return dispatch({
362+
type: CREATE_PROJECT_PHASE_TIMELINE_MILESTONES,
363+
payload: createPhaseAndMilestonesRequest(project, projectTemplate, status, startDate, endDate, milestones)
364+
})
365+
}
366+
}
367+
331368
export function deleteProjectPhase(projectId, phaseId) {
332369
return (dispatch) => {
333370
return dispatch({
@@ -433,11 +470,9 @@ export function updatePhase(projectId, phaseId, updatedProps, phaseIndex) {
433470
const phaseStartDate = timeline ? timeline.startDate : phase.startDate
434471
const startDateChanged = updatedProps.startDate ? updatedProps.startDate.diff(phaseStartDate) : null
435472
const phaseActivated = phaseStatusChanged && updatedProps.status === PHASE_STATUS_ACTIVE
436-
if (phaseActivated) {
437-
const duration = updatedProps.duration ? updatedProps.duration : phase.duration
438-
updatedProps.startDate = moment().utc().hours(0).minutes(0).seconds(0).milliseconds(0).format('YYYY-MM-DD')
439-
updatedProps.endDate = moment(updatedProps.startDate).add(duration - 1, 'days').format('YYYY-MM-DD')
440-
}
473+
474+
updatedProps.startDate = moment(updatedProps.startDate).format('YYYY-MM-DD')
475+
updatedProps.endDate = moment(updatedProps.endDate).format('YYYY-MM-DD')
441476

442477
return dispatch({
443478
type: UPDATE_PHASE,
@@ -638,4 +673,4 @@ export function loadProjectMember(projectId, memberId) {
638673
payload: getProjectMember(projectId, memberId)
639674
})
640675
}
641-
}
676+
}

0 commit comments

Comments
 (0)