def test_nr_issues_multiple_sources(self, query_sum_mock):
        """ Test that the field is summed correctly and that involved issues are in the issue list. """
        jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='customfield_11700')
        query_sum_mock.return_value = (0, ([]))

        jira_filter.nr_issues('12345', '78910')

        self.assertTrue(query_sum_mock.has_calls([call('12345'), call('78910')]))
    def test_nr_issues_with_empty_jira_answer(self, get_query_url_mock):
        """ Test that the sum is -1 and the issue list is empty when jira returns empty json. """
        jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='customfield_11700')
        get_query_url_mock.return_value = []

        result, issue_list = jira_filter.nr_issues('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual(-1, result)
        self.assertEqual([], issue_list)
    def test_issues_with_field_without_issues(self, get_query_url_mock):
        """ Test that the issue list is empty when there are no issues. """
        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": []}

        issue_list = jira_filter.issues_with_field('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual([], issue_list)
    def test_nr_issues_when_no_issues(self, get_query_url_mock):
        """ Test that the number of issues is zero and the issue list is empty when there is no issues. """
        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": "0", "issues": []}

        result, issue_list = jira_filter.nr_issues('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual(0, result)  # pay attention, it reads 'total' and does not count the issues
        self.assertEqual([], issue_list)
    def test_issues_with_field_with_empty_jira_answer(self, get_query_url_mock, mock_error):
        """ Test that the issue list is empty when jira returns empty json. """
        jira_filter = JiraFilter('http://jira/', 'username', 'password', field_name='customfield_11700')
        get_query_url_mock.return_value = []

        issue_list = jira_filter.issues_with_field('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual([], issue_list)
        self.assertEqual("Couldn't get issues from Jira filter %s: %s.", mock_error.call_args_list[0][0][0])
        self.assertEqual("12345", mock_error.call_args_list[0][0][1])
        self.assertIsInstance(mock_error.call_args_list[0][0][2], TypeError)
    def test_nr_issues_with_field_omitted(self, get_query_url_mock):
        """ Test that the number of items equals those that do not have specified field. """
        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"}},
                {"key": "ISS-2", "fields": {"summary": "2nd Issue", "customfield_11700": 100}}]}

        result, issue_list = jira_filter.nr_issues_with_field_empty('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual(1, result)
        self.assertEqual([{"href": "http://jira/browse/ISS-1", "text": "First Issue"}], issue_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)
    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_without_issues(self, get_query_url_mock):
        """ Test that the issue list is empty when there are no issues. """
        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": []}

        issue_list = jira_filter.issues_with_field('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual([], issue_list)
    def test_nr_issues_with_empty_jira_answer(self, get_query_url_mock):
        """ Test that the sum is -1 and the issue list is empty when jira returns empty json. """
        jira_filter = JiraFilter('http://jira/',
                                 'username',
                                 'password',
                                 field_name='customfield_11700')
        get_query_url_mock.return_value = []

        result, issue_list = jira_filter.nr_issues('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual(-1, result)
        self.assertEqual([], issue_list)
    def test_nr_issues_multiple_sources(self, query_sum_mock):
        """ Test that the field is summed correctly and that involved issues are in the issue list. """
        jira_filter = JiraFilter('http://jira/',
                                 'username',
                                 'password',
                                 field_name='customfield_11700')
        query_sum_mock.return_value = (0, ([]))

        jira_filter.nr_issues('12345', '78910')

        self.assertTrue(
            query_sum_mock.has_calls([call('12345'),
                                      call('78910')]))
    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_nr_issues_with_field_empty_when_no_issues(self,
                                                       get_query_url_mock):
        """ Test that the number of issues is zero and the issue list is empty when there is no issues. """
        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": []}

        result, issue_list = jira_filter.nr_issues_with_field_empty('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual(0, result)
        self.assertEqual([], 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_nr_issues(self, get_query_url_mock):
        """ Test that the number of items equals the sum of totals per metric source returned by Jira. """
        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}}]}

        result, issue_list = jira_filter.nr_issues('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual(5, result)  # pay attention, it reads 'total' and does not count the issues
        self.assertEqual([
            {"href": "http://jira/browse/ISS-1", "text": "First Issue"},
            {"href": "http://jira/browse/ISS-2", "text": "2nd Issue"}
        ], issue_list)
    def test_issues_with_field_with_empty_jira_answer(self, get_query_url_mock,
                                                      mock_error):
        """ Test that the issue list is empty when jira returns empty json. """
        jira_filter = JiraFilter('http://jira/',
                                 'username',
                                 'password',
                                 field_name='customfield_11700')
        get_query_url_mock.return_value = []

        issue_list = jira_filter.issues_with_field('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual([], issue_list)
        self.assertEqual("Couldn't get issues from Jira filter %s: %s.",
                         mock_error.call_args_list[0][0][0])
        self.assertEqual("12345", mock_error.call_args_list[0][0][1])
        self.assertIsInstance(mock_error.call_args_list[0][0][2], TypeError)
    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_nr_issues_with_field_omitted(self, get_query_url_mock):
        """ Test that the number of items equals those that do not have specified field. """
        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"}},
                {"key": "ISS-2", "fields": {"summary": "2nd Issue", "customfield_11700": 100}}]}

        result, issue_list = jira_filter.nr_issues_with_field_empty('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual(1, result)
        self.assertEqual([{
            "href": "http://jira/browse/ISS-1",
            "text": "First Issue"
        }], 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_url(self, get_query_url_mock):
        """ Test that the Jira filter returns the correct urls for the filters. """
        jira1 = 'http://jira1/view'
        jira2 = 'http://jira2/view'
        get_query_url_mock.side_effect = [jira1, jira2]

        result = JiraFilter('', '', '').metric_source_urls(123, 567)

        get_query_url_mock.assert_has_calls(
            [call(123, search=False),
             call(567, search=False)])
        self.assertEqual([jira1, jira2], result)
    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_sum_field(self, get_query_url_mock):
        """ Test that the field is summed correctly and that involved issues are in the issue list. """
        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}}]}

        result, issue_list = jira_filter.sum_field('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual(120.3, result)
        self.assertEqual([{
            "href": "http://jira/browse/ISS-1",
            "text": "First Issue"
        }, {
            "href": "http://jira/browse/ISS-2",
            "text": "2nd Issue"
        }], issue_list)
    def test_nr_issues(self, get_query_url_mock):
        """ Test that the number of items equals the sum of totals per metric source returned by Jira. """
        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}}]}

        result, issue_list = jira_filter.nr_issues('12345')

        get_query_url_mock.assert_called_once()
        self.assertEqual(
            5, result
        )  # pay attention, it reads 'total' and does not count the issues
        self.assertEqual([{
            "href": "http://jira/browse/ISS-1",
            "text": "First Issue"
        }, {
            "href": "http://jira/browse/ISS-2",
            "text": "2nd Issue"
        }], issue_list)
Beispiel #26
0
 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)
 def test_get_issue_url(self):
     """ Test Jira issue url formatting. """
     jira_filter = JiraFilter('url/', '', '')
     self.assertEqual('url/browse/key', jira_filter.get_issue_url('key'))
Beispiel #28
0
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
        ])
 def test_get_issue_url(self):
     """ Test Jira issue url formatting. """
     jira_filter = JiraFilter('url/', '', '')
     self.assertEqual('url/browse/key', jira_filter.get_issue_url('key'))