def test_issues_with_field_exceeding_value_invalid2(self, get_query_url_mock, mock_error): """ Test that the errors with issues having invalid data are logged. """ jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='customfield_11700') get_query_url_mock.return_value = \ {"searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [ {"key": "ISS-1", "no-fields": ''}]} issue_list = jira_filter.issues_with_field_exceeding_value('12345', limit_value=25) get_query_url_mock.assert_called_once() self.assertEqual([], issue_list) self.assertEqual("Error processing jira issues: %s.", mock_error.call_args_list[0][0][0]) self.assertIsInstance(mock_error.call_args_list[0][0][1], KeyError)
def test_issues_with_field_exceeding_value(self, get_query_url_mock): """ Test that the issues are returned correctly with their field values. """ jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='customfield_11700') get_query_url_mock.return_value = \ {"searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [ {"key": "ISS-1", "fields": {"summary": "First Issue", "customfield_11700": 20.3}}, {"key": "ISS-2", "fields": {"summary": "2nd Issue", "customfield_11700": 100}}, {"key": "ISS-3", "fields": {"summary": "The Last Issue", "customfield_11700": None}}]} issue_list = jira_filter.issues_with_field_exceeding_value('12345', limit_value=25) get_query_url_mock.assert_called_once() self.assertEqual([ ("http://jira/browse/ISS-1", "First Issue", 20.3) ], issue_list)
def test_issues_with_field_exceeding_value_2_sources(self, get_query_url_mock): """ Test that the issues are returned correctly with their field values. """ jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='customfield_11700') get_query_url_mock.side_effect = [ {"searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [ {"key": "ISS-1", "fields": {"summary": "First Issue", "customfield_11700": 20.3}}]}, {"searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [ {"key": "ISS-11", "fields": {"summary": "Issue 11", "customfield_11700": 11}}]}] issue_list = jira_filter.issues_with_field_exceeding_value('12345', '22345', limit_value=25) self.assertTrue(get_query_url_mock.has_calls([call('12345'), call('22345')])) self.assertEqual([ ("http://jira/browse/ISS-1", "First Issue", 20.3), ("http://jira/browse/ISS-11", "Issue 11", 11) ], issue_list)
def test_issues_with_field_exceeding_value_2_sources( self, get_query_url_mock): """ Test that the issues are returned correctly with their field values. """ jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='customfield_11700') get_query_url_mock.side_effect = [{ "searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [{ "key": "ISS-1", "fields": { "summary": "First Issue", "customfield_11700": 20.3 } }] }, { "searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [{ "key": "ISS-11", "fields": { "summary": "Issue 11", "customfield_11700": 11 } }] }] issue_list = jira_filter.issues_with_field_exceeding_value( '12345', '22345', limit_value=25) self.assertTrue( get_query_url_mock.has_calls([call('12345'), call('22345')])) self.assertEqual([("http://jira/browse/ISS-1", "First Issue", 20.3), ("http://jira/browse/ISS-11", "Issue 11", 11)], issue_list)
def test_issues_with_field_exceeding_value(self, get_query_url_mock): """ Test that the issues are returned correctly with their field values. """ jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='customfield_11700') get_query_url_mock.return_value = \ {"searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [ {"key": "ISS-1", "fields": {"summary": "First Issue", "customfield_11700": 20.3}}, {"key": "ISS-2", "fields": {"summary": "2nd Issue", "customfield_11700": 100}}, {"key": "ISS-3", "fields": {"summary": "The Last Issue", "customfield_11700": None}}]} issue_list = jira_filter.issues_with_field_exceeding_value( '12345', limit_value=25) get_query_url_mock.assert_called_once() self.assertEqual([("http://jira/browse/ISS-1", "First Issue", 20.3)], issue_list)
def test_issues_with_field_exceeding_value_date(self, get_query_url_mock): """ Test that the issues are returned correctly with their field values. """ jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='duedate') get_query_url_mock.return_value = \ {"searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [ {"key": "ISS-1", "fields": {"summary": "First Issue", "custom": 20.3, "duedate": "2018-10-22T19:12:36.000+0100"}}, {"key": "ISS-2", "fields": {"summary": "2nd Issue", "custom": 100, "duedate": "2017-12-28T13:10:46.000+0100"}}, {"key": "ISS-3", "fields": {"summary": "The Last Issue", "custom": 55, "duedate": None}}]} issue_list = jira_filter.issues_with_field_exceeding_value( '12345', limit_value="2018-03-22T19:12", extra_fields=['custom', 'non-existent-field']) get_query_url_mock.assert_called_once() self.assertEqual([ ("http://jira/browse/ISS-2", "2nd Issue", "2017-12-28T13:10:46.000+0100", 100, None) ], issue_list)
def test_issues_with_field_exceeding_value_invalid2( self, get_query_url_mock, mock_error): """ Test that the errors with issues having invalid data are logged. """ jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='customfield_11700') get_query_url_mock.return_value = \ {"searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [ {"key": "ISS-1", "no-fields": ''}]} issue_list = jira_filter.issues_with_field_exceeding_value( '12345', limit_value=25) get_query_url_mock.assert_called_once() self.assertEqual([], issue_list) self.assertEqual("Error processing jira issues: %s.", mock_error.call_args_list[0][0][0]) self.assertIsInstance(mock_error.call_args_list[0][0][1], KeyError)
def test_issues_with_field_exceeding_value_date(self, get_query_url_mock): """ Test that the issues are returned correctly with their field values. """ jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='duedate') get_query_url_mock.return_value = \ {"searchUrl": "http://jira/search", "viewUrl": "http://jira/view", "total": "5", "issues": [ {"key": "ISS-1", "fields": {"summary": "First Issue", "custom": 20.3, "duedate": "2018-10-22T19:12:36.000+0100"}}, {"key": "ISS-2", "fields": {"summary": "2nd Issue", "custom": 100, "duedate": "2017-12-28T13:10:46.000+0100"}}, {"key": "ISS-3", "fields": {"summary": "The Last Issue", "custom": 55, "duedate": None}}]} issue_list = jira_filter.issues_with_field_exceeding_value( '12345', limit_value="2018-03-22T19:12", extra_fields=['custom', 'non-existent-field']) get_query_url_mock.assert_called_once() self.assertEqual([("http://jira/browse/ISS-2", "2nd Issue", "2017-12-28T13:10:46.000+0100", 100, None)], issue_list)
class JiraActionList(ActionLog): """ Jira used as an action list """ metric_source_name = 'Jira Action List' def __init__(self, url: str, username: str, password: str, field_name: str = 'duedate', *args, **kwargs) -> None: self._fields_to_ignore = kwargs.pop('fields_to_ignore', []) self.__url = url self.__field_name = field_name self.__jira_filter = JiraFilter(self.__url, username, password, self.__field_name) super().__init__(*args, **kwargs) @classmethod def _is_str_date_before(cls, str_date: str, limit_date: datetime.datetime) -> bool: return utils.parse_iso_datetime_local_naive(str_date) < limit_date def _get_issues_older_than(self, *metric_source_ids: str, limit_date: datetime.datetime) -> List[Tuple[str, str, str]]: try: extra_fields = ['updated', 'created'] + [list(field.keys())[0] for field in self._fields_to_ignore] issues = self.__jira_filter.issues_with_field_exceeding_value( *metric_source_ids, extra_fields=extra_fields, compare=self._is_str_date_before, limit_value=limit_date) return [i for i in issues if not self.__should_issue_be_ignored(i)] except IndexError as reason: logging.error("Jira filter result for overdue issues inadequate. Reason: %s.", reason) return None def __should_issue_be_ignored(self, issue) -> bool: for index, ignore in enumerate(self._fields_to_ignore): if issue[index + 5] == list(ignore.values())[0]: return True return False def ignored_lists(self) -> List[str]: """ Return the ignored lists. """ return self._fields_to_ignore def nr_of_over_due_actions(self, *metric_source_ids: str) -> int: """ Return the number of over due actions. """ issue_list = self._get_issues_older_than(*metric_source_ids, limit_date=datetime.datetime.now()) return len(issue_list) if issue_list is not None else -1 def over_due_actions_url(self, *metric_source_ids: str) -> List[Tuple[str, str, str]]: """ Return the urls to the over due actions. """ issue_list = self._get_issues_older_than(*metric_source_ids, limit_date=datetime.datetime.now()) return [(issue[0], issue[1], self.__get_formatted_time_delta(issue[2])) for issue in issue_list] \ if issue_list is not None else [] def nr_of_inactive_actions(self, *metric_source_ids: str) -> int: """ Return the number of inactive actions. """ issue_list = self._get_issues_inactive_for(*metric_source_ids) return len(issue_list) if issue_list is not None else -1 def inactive_actions_url(self, *metric_source_ids: str) -> List[Tuple[str, str, str]]: """ Return the urls for the inactive actions. """ issue_list = self._get_issues_inactive_for(*metric_source_ids) return [(issue[0], issue[1], self.__get_formatted_time_delta(issue[3])) for issue in issue_list] \ if issue_list is not None else [] def _get_issues_inactive_for(self, *metric_source_ids, delta: relativedelta = relativedelta(days=14)): try: overdue_issue_list = self._get_issues_older_than(*metric_source_ids, limit_date=datetime.datetime.now()) return [issue for issue in overdue_issue_list if issue[3] is not None and utils.parse_iso_datetime_local_naive(issue[3]) <= (datetime.datetime.now() - delta)] except IndexError as reason: logging.error("Jira filter result for inactive issues inadequate. Reason: %s.", reason) return None @classmethod def __get_formatted_time_delta(cls, date_to_parse) -> str: return utils.format_timedelta(datetime.datetime.now().astimezone() - dateutil.parser.parse(date_to_parse)) def metric_source_urls(self, *metric_source_ids: str) -> List[str]: """ Return the url(s) to the metric source for the metric source id. """ return self.__jira_filter.metric_source_urls(*metric_source_ids) def datetime(self, *metric_source_ids: str) -> datetime.datetime: # pylint: disable=unused-argument,no-self-use """ Return the date and time of the last measurement. """ issue_list = self._get_issues_older_than(*metric_source_ids, limit_date=datetime.datetime.now()) return max([max(utils.parse_iso_datetime_local_naive(issue[4]), utils.parse_iso_datetime_local_naive(issue[3]) if issue[3] else datetime.datetime.min) for issue in issue_list])
class JiraActionList(ActionLog): """ Jira used as an action list """ metric_source_name = 'Jira Action List' def __init__(self, url: str, username: str, password: str, field_name: str = 'duedate', *args, **kwargs) -> None: self._fields_to_ignore = kwargs.pop('fields_to_ignore', []) self.__url = url self.__field_name = field_name self.__jira_filter = JiraFilter(self.__url, username, password, self.__field_name) super().__init__(*args, **kwargs) @classmethod def _is_str_date_before(cls, str_date: str, limit_date: datetime.datetime) -> bool: return utils.parse_iso_datetime_local_naive(str_date) < limit_date def _get_issues_older_than( self, *metric_source_ids: str, limit_date: datetime.datetime) -> List[Tuple[str, str, str]]: try: extra_fields = ['updated', 'created'] + [ list(field.keys())[0] for field in self._fields_to_ignore ] issues = self.__jira_filter.issues_with_field_exceeding_value( *metric_source_ids, extra_fields=extra_fields, compare=self._is_str_date_before, limit_value=limit_date) return [i for i in issues if not self.__should_issue_be_ignored(i)] except IndexError as reason: logging.error( "Jira filter result for overdue issues inadequate. Reason: %s.", reason) return None def __should_issue_be_ignored(self, issue) -> bool: for index, ignore in enumerate(self._fields_to_ignore): if issue[index + 5] == list(ignore.values())[0]: return True return False def ignored_lists(self) -> List[str]: """ Return the ignored lists. """ return self._fields_to_ignore def nr_of_over_due_actions(self, *metric_source_ids: str) -> int: """ Return the number of over due actions. """ issue_list = self._get_issues_older_than( *metric_source_ids, limit_date=datetime.datetime.now()) return len(issue_list) if issue_list is not None else -1 def over_due_actions_url( self, *metric_source_ids: str) -> List[Tuple[str, str, str]]: """ Return the urls to the over due actions. """ issue_list = self._get_issues_older_than( *metric_source_ids, limit_date=datetime.datetime.now()) return [(issue[0], issue[1], self.__get_formatted_time_delta(issue[2])) for issue in issue_list] \ if issue_list is not None else [] def nr_of_inactive_actions(self, *metric_source_ids: str) -> int: """ Return the number of inactive actions. """ issue_list = self._get_issues_inactive_for(*metric_source_ids) return len(issue_list) if issue_list is not None else -1 def inactive_actions_url( self, *metric_source_ids: str) -> List[Tuple[str, str, str]]: """ Return the urls for the inactive actions. """ issue_list = self._get_issues_inactive_for(*metric_source_ids) return [(issue[0], issue[1], self.__get_formatted_time_delta(issue[3])) for issue in issue_list] \ if issue_list is not None else [] def _get_issues_inactive_for( self, *metric_source_ids, delta: relativedelta = relativedelta(days=14)): try: overdue_issue_list = self._get_issues_older_than( *metric_source_ids, limit_date=datetime.datetime.now()) return [ issue for issue in overdue_issue_list if issue[3] is not None and utils.parse_iso_datetime_local_naive(issue[3]) <= (datetime.datetime.now() - delta) ] except IndexError as reason: logging.error( "Jira filter result for inactive issues inadequate. Reason: %s.", reason) return None @classmethod def __get_formatted_time_delta(cls, date_to_parse) -> str: return utils.format_timedelta(datetime.datetime.now().astimezone() - dateutil.parser.parse(date_to_parse)) def metric_source_urls(self, *metric_source_ids: str) -> List[str]: """ Return the url(s) to the metric source for the metric source id. """ return self.__jira_filter.metric_source_urls(*metric_source_ids) def datetime(self, *metric_source_ids: str) -> datetime.datetime: # pylint: disable=unused-argument,no-self-use """ Return the date and time of the last measurement. """ issue_list = self._get_issues_older_than( *metric_source_ids, limit_date=datetime.datetime.now()) return max([ max( utils.parse_iso_datetime_local_naive(issue[4]), utils.parse_iso_datetime_local_naive(issue[3]) if issue[3] else datetime.datetime.min) for issue in issue_list ])