Skip to content

Commit

Permalink
Merge pull request #19 from melexis/approved-status
Browse files Browse the repository at this point in the history
Fix end date of approved Change Requests and issues with multiple transitions to Approved/Resolved
  • Loading branch information
Ben2022 authored May 27, 2022
2 parents 58b6c05 + 0d8b21c commit 3f10408
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 23 deletions.
38 changes: 26 additions & 12 deletions src/mlx/jira_juggler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from datetime import datetime, time
from functools import cmp_to_key
from getpass import getpass
from operator import attrgetter

from dateutil import parser
from decouple import config
Expand Down Expand Up @@ -188,8 +189,8 @@ def load_from_jira_issue(self, jira_issue):
self.value = getattr(item, 'from', None)
else:
self.value = item.to
return # got last assignee before transition to Resolved status
elif item.field.lower() == 'status' and item.toString.lower() == 'resolved':
return # got last assignee before transition to Approved/Resolved status
elif item.field.lower() == 'status' and item.toString.lower() in ('approved', 'resolved'):
before_resolved = True
if self.value and self.value != self.DEFAULT_VALUE:
return # assignee was changed after transition to Resolved status
Expand Down Expand Up @@ -351,6 +352,7 @@ def __init__(self, jira_issue=None):
self.summary = self.DEFAULT_SUMMARY
self.properties = {}
self.issue = None
self._resolved_at_date = None

if jira_issue:
self.load_from_jira_issue(jira_issue)
Expand All @@ -368,6 +370,8 @@ def load_from_jira_issue(self, jira_issue):
self.properties['allocate'] = JugglerTaskAllocate(jira_issue)
self.properties['effort'] = JugglerTaskEffort(jira_issue)
self.properties['depends'] = JugglerTaskDepends(jira_issue)
if self.is_resolved:
self.resolved_at_date = self.determine_resolved_at_date()

def validate(self, tasks):
"""Validates (and corrects) the current task
Expand Down Expand Up @@ -397,8 +401,8 @@ def __str__(self):

@property
def is_resolved(self):
"""bool: True if JIRA issue has been resolved; False otherwise"""
return self.issue is not None and self.issue.fields.status.name in ('Closed', 'Resolved')
"""bool: True if JIRA issue has been approved/resolved/closed; False otherwise"""
return self.issue is not None and self.issue.fields.status.name in ('Approved', 'Resolved', 'Closed')

@property
def resolved_at_repr(self):
Expand All @@ -412,15 +416,26 @@ def resolved_at_repr(self):

@property
def resolved_at_date(self):
"""datetime.datetime: Date and time corresponding to the last transition to the Resolved status; None when not
resolved
"""datetime.datetime: Date and time corresponding to the last transition to the Approved/Resolved status; the
transition to the Closed status is used as fallback; None when not resolved
"""
if self.is_resolved:
for change in self.issue.changelog.histories:
for item in change.items[::-1]:
if item.field.lower() == 'status' and item.toString.lower() in ('resolved', 'closed'):
return self._resolved_at_date

@resolved_at_date.setter
def resolved_at_date(self, value):
self._resolved_at_date = value

def determine_resolved_at_date(self):
closed_at_date = None
for change in sorted(self.issue.changelog.histories, key=attrgetter('created'), reverse=True):
for item in change.items:
if item.field.lower() == 'status':
status = item.toString.lower()
if status in ('approved', 'resolved'):
return parser.isoparse(change.created)
return None
elif status in ('closed',) and closed_at_date is None:
closed_at_date = parser.isoparse(change.created)
return closed_at_date


class JiraJuggler:
Expand Down Expand Up @@ -477,7 +492,6 @@ def load_issues_from_jira(self, depend_on_preceding=False, sprint_field_name='',
busy = False

self.issue_count += len(issues)

for issue in issues:
logging.debug('Retrieved %s: %s', issue.key, issue.fields.summary)
tasks.append(JugglerTask(issue))
Expand Down
31 changes: 20 additions & 11 deletions tests/test_jira_juggler.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ def test_task_double_depends(self, jira_mock):

@patch('mlx.jira_juggler.JIRA', autospec=True)
def test_resolved_task(self, jira_mock):
'''Test that the last assignee in the Analyzed state is used and the Time Spent is used as effort'''
'''Test that the last assignee in the Analyzed state is used and the Time Spent is used as effort
Test that the most recent transition to the Approved/Resolved state is used to mark the end'''
jira_mock_object = MagicMock(spec=JIRA)
jira_mock.return_value = jira_mock_object
juggler = dut.JiraJuggler(self.URL, self.USER, self.PASSWD, self.QUERY)
Expand All @@ -308,41 +309,46 @@ def test_resolved_task(self, jira_mock):
'items': [{
'field': 'assignee',
'to': self.ASSIGNEE1,
}]
}],
'created': '2022-04-08T13:11:47.749+0200',
},
{
'items': [{
'field': 'status',
'toString': 'Resolved',
}]
}],
'created': '2022-04-11T08:13:14.350+0200',
},
{
'items': [{
'field': 'assignee',
'to': self.ASSIGNEE3,
# 'from': self.ASSIGNEE1, # cannot use 'from' as key to test
}]
}],
'created': '2022-04-12T13:04:11.449+0200',
},
{
'items': [{
'field': 'status',
'toString': 'Analyzed',
}]
}],
'created': '2022-04-13T14:10:43.632+0200',
},
{
'items': [{
'field': 'assignee',
'to': self.ASSIGNEE2,
}]
}],
'created': '2022-05-02T09:20:36.310+0200',
},
{
'items': [{
'field': 'status',
'toString': 'Resolved',
}]
'toString': 'Approved',
}],
'created': '2022-05-25T14:07:11.974+0200',
},
]

jira_mock_object.search_issues.side_effect = [[self._mock_jira_issue(self.KEY1,
self.SUMMARY1,
self.ASSIGNEE1,
Expand All @@ -356,6 +362,7 @@ def test_resolved_task(self, jira_mock):
self.assertEqual(1, len(issues))
self.assertEqual(self.ASSIGNEE2, issues[0].properties['allocate'].value)
self.assertEqual(self.ESTIMATE2 / self.SECS_PER_DAY, issues[0].properties['effort'].value)
self.assertEqual('2022-05-25 14:07:11.974000+02:00', str(issues[0].resolved_at_date))

@patch('mlx.jira_juggler.JIRA', autospec=True)
def test_closed_task(self, jira_mock):
Expand All @@ -371,13 +378,15 @@ def test_closed_task(self, jira_mock):
'items': [{
'field': 'status',
'toString': 'Resolved',
}]
}],
'created': '2022-04-12T13:04:11.449+0200',
},
{
'items': [{
'field': 'assignee',
'to': self.ASSIGNEE2,
}]
}],
'created': '2022-05-25T14:07:11.974+0200',
},
]

Expand Down

0 comments on commit 3f10408

Please sign in to comment.