t-bug-catcher 0.6.10__tar.gz → 0.6.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/PKG-INFO +1 -1
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/setup.cfg +1 -1
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/setup.py +1 -1
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/__init__.py +1 -1
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/jira.py +44 -15
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher.egg-info/PKG-INFO +1 -1
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/tests/test_t_bug_catcher.py +32 -11
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/MANIFEST.in +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/README.rst +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/pyproject.toml +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/requirements.txt +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/bug_catcher.py +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/bug_snag.py +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/config.py +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/exceptions.py +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/resources/whispers_config.yml +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/stack_saver.py +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/utils/__init__.py +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/utils/common.py +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/utils/logger.py +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher/workitems.py +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher.egg-info/SOURCES.txt +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher.egg-info/not-zip-safe +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher.egg-info/requires.txt +0 -0
- {t_bug_catcher-0.6.10 → t_bug_catcher-0.6.12}/t_bug_catcher.egg-info/top_level.txt +0 -0
|
@@ -26,7 +26,7 @@ setup(
|
|
|
26
26
|
packages=find_packages(include=["t_bug_catcher", "t_bug_catcher.*"]),
|
|
27
27
|
test_suite="tests",
|
|
28
28
|
url="https://www.thoughtful.ai/",
|
|
29
|
-
version="0.6.
|
|
29
|
+
version="0.6.12",
|
|
30
30
|
zip_safe=False,
|
|
31
31
|
install_requires=install_requirements,
|
|
32
32
|
include_package_data=True,
|
|
@@ -58,6 +58,15 @@ class Jira:
|
|
|
58
58
|
self._auth = None
|
|
59
59
|
self._default_assignee = None
|
|
60
60
|
self._build_info: Optional[dict] = None
|
|
61
|
+
self._status_to_transition = ["to do", "open", "backlog"]
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def _get_package_version() -> str:
|
|
65
|
+
"""Get the package version safely."""
|
|
66
|
+
try:
|
|
67
|
+
return version("t_bug_catcher")
|
|
68
|
+
except Exception:
|
|
69
|
+
return "unknown"
|
|
61
70
|
|
|
62
71
|
@staticmethod
|
|
63
72
|
def _is_json_response(response) -> bool:
|
|
@@ -165,15 +174,19 @@ class Jira:
|
|
|
165
174
|
project_key = project_key or self._project_key
|
|
166
175
|
jql_query = f'project = "{project_key}"'
|
|
167
176
|
|
|
168
|
-
#
|
|
169
|
-
|
|
177
|
+
# Use POST method with JSON body as recommended by the API documentation
|
|
178
|
+
request_body = {
|
|
179
|
+
"jql": jql_query,
|
|
180
|
+
"maxResults": 100,
|
|
181
|
+
"fields": ["id", "key", "summary", "description", "status", "assignee", "attachment", "comment"],
|
|
182
|
+
}
|
|
170
183
|
|
|
171
184
|
response = requests.request(
|
|
172
|
-
"
|
|
173
|
-
self._base_url + "/rest/
|
|
185
|
+
"POST",
|
|
186
|
+
self._base_url + "/rest/3/search/jql",
|
|
174
187
|
headers=self.__get_headers(),
|
|
175
188
|
auth=self._auth,
|
|
176
|
-
|
|
189
|
+
json=request_body,
|
|
177
190
|
)
|
|
178
191
|
self.check_response(response)
|
|
179
192
|
return response.json()
|
|
@@ -310,7 +323,7 @@ class Jira:
|
|
|
310
323
|
Returns:
|
|
311
324
|
dict: The board information
|
|
312
325
|
"""
|
|
313
|
-
if self._transition_types.get(
|
|
326
|
+
if any(self._transition_types.get(status) for status in self._status_to_transition):
|
|
314
327
|
return self._transition_types
|
|
315
328
|
|
|
316
329
|
response = requests.request(
|
|
@@ -761,7 +774,7 @@ class Jira:
|
|
|
761
774
|
},
|
|
762
775
|
{
|
|
763
776
|
"type": "text",
|
|
764
|
-
"text": f" (v{
|
|
777
|
+
"text": f" (v{Jira._get_package_version()})",
|
|
765
778
|
"marks": [
|
|
766
779
|
{"type": "em"},
|
|
767
780
|
{"type": "subsup", "attrs": {"type": "sub"}},
|
|
@@ -1120,7 +1133,8 @@ class Jira:
|
|
|
1120
1133
|
auth=self._auth,
|
|
1121
1134
|
)
|
|
1122
1135
|
self.check_response(response)
|
|
1123
|
-
|
|
1136
|
+
response_data = response.json()
|
|
1137
|
+
return response_data.get("fields", {}).get("status", {}).get("name", "Unknown")
|
|
1124
1138
|
|
|
1125
1139
|
def __update_existing_ticket(
|
|
1126
1140
|
self,
|
|
@@ -1145,10 +1159,24 @@ class Jira:
|
|
|
1145
1159
|
"""
|
|
1146
1160
|
issue_status = self.check_issue_status(existing_ticket["id"])
|
|
1147
1161
|
self._transition_types = self.__get_transtion_types(issue_id=existing_ticket["id"])
|
|
1148
|
-
|
|
1162
|
+
transition_id = next(
|
|
1163
|
+
(
|
|
1164
|
+
self._transition_types.get(status)
|
|
1165
|
+
for status in self._status_to_transition
|
|
1166
|
+
if self._transition_types.get(status)
|
|
1167
|
+
),
|
|
1168
|
+
None,
|
|
1169
|
+
)
|
|
1170
|
+
if not transition_id:
|
|
1171
|
+
transitions_str = [k for k in self._transition_types.keys()]
|
|
1172
|
+
raise BadRequestError(
|
|
1173
|
+
f"Transition ID not found for statuses: {self._status_to_transition}. "
|
|
1174
|
+
f"Available transitions: {transitions_str}"
|
|
1175
|
+
)
|
|
1176
|
+
if issue_status.lower() not in self._status_to_transition:
|
|
1149
1177
|
self.issue_transition(
|
|
1150
1178
|
ticket_id=existing_ticket["id"],
|
|
1151
|
-
transition_id=
|
|
1179
|
+
transition_id=transition_id,
|
|
1152
1180
|
)
|
|
1153
1181
|
self.update_comment(
|
|
1154
1182
|
ticket_id=existing_ticket["id"],
|
|
@@ -1158,7 +1186,7 @@ class Jira:
|
|
|
1158
1186
|
)
|
|
1159
1187
|
issue = self.get_issue(existing_ticket["id"])
|
|
1160
1188
|
|
|
1161
|
-
if len(issue
|
|
1189
|
+
if len(issue.get("fields", {}).get("attachment", [])) >= CONFIG.LIMITS.MAX_ISSUE_ATTACHMENTS:
|
|
1162
1190
|
logger.warning(
|
|
1163
1191
|
f"Attachments were not uploaded due to exceeding "
|
|
1164
1192
|
f"{CONFIG.LIMITS.MAX_ISSUE_ATTACHMENTS} attachments limit."
|
|
@@ -1697,9 +1725,10 @@ class Jira:
|
|
|
1697
1725
|
dict or None: The matching ticket if found, otherwise None.
|
|
1698
1726
|
"""
|
|
1699
1727
|
for ticket in all_tickets:
|
|
1700
|
-
|
|
1728
|
+
description = ticket.get("fields", {}).get("description", "")
|
|
1729
|
+
if not description:
|
|
1701
1730
|
continue
|
|
1702
|
-
if f"{error_id}_~" not in
|
|
1731
|
+
if f"{error_id}_~" not in description:
|
|
1703
1732
|
continue
|
|
1704
1733
|
return ticket
|
|
1705
1734
|
|
|
@@ -1797,8 +1826,8 @@ class Jira:
|
|
|
1797
1826
|
logger.warning(f"Failed to convert exception to string due to: {e}")
|
|
1798
1827
|
return exception.__class__.__name__
|
|
1799
1828
|
|
|
1800
|
-
message = re.sub("<([a-z]+)(?![^>]*\/>)[^>]*>", r"<\1>", exception_str)
|
|
1801
|
-
message = re.sub(">([^<]+)
|
|
1829
|
+
message = re.sub(r"<([a-z]+)(?![^>]*\/>)[^>]*>", r"<\1>", exception_str)
|
|
1830
|
+
message = re.sub(r">([^<]+)</", ">...</", message)
|
|
1802
1831
|
|
|
1803
1832
|
if "selenium" not in exception.__class__.__name__.lower() and not isinstance(exception, AssertionError):
|
|
1804
1833
|
return message
|
|
@@ -50,22 +50,43 @@ class TestBugCatcher(unittest.TestCase):
|
|
|
50
50
|
assert hasattr(ex, "custom_attachments")
|
|
51
51
|
|
|
52
52
|
@patch.object(CONFIG, "ENVIRONMENT", "robocloud")
|
|
53
|
-
def
|
|
54
|
-
"""Test report_error function."""
|
|
53
|
+
def test_report_error_success(self):
|
|
54
|
+
"""Test report_error function when everything works correctly."""
|
|
55
55
|
with patch.object(
|
|
56
56
|
self.bug_catcher, "_BugCatcher__configurator", new_callable=PropertyMock
|
|
57
57
|
) as mock_configurator:
|
|
58
58
|
patch.object(mock_configurator, "is_jira_configured", return_value=True)
|
|
59
59
|
patch.object(mock_configurator, "is_bugsnag_configured", return_value=True)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
with patch.object(self.bug_catcher._BugCatcher__jira, "report_error") as mock_jira_report:
|
|
61
|
+
mock_jira_report.return_value = {"key": "TEST-123", "id": "10001"}
|
|
62
|
+
try:
|
|
63
|
+
raise Exception("Test exception")
|
|
64
|
+
except Exception as ex:
|
|
65
|
+
with patch.object(logger, "info") as mock_info:
|
|
66
|
+
self.bug_catcher.report_error(exception=ex)
|
|
67
|
+
mock_jira_report.assert_called_once()
|
|
68
|
+
mock_info.assert_called()
|
|
69
|
+
if mock_info.call_args:
|
|
70
|
+
info_message = mock_info.call_args[0][0]
|
|
71
|
+
self.assertTrue("reported" in info_message.lower())
|
|
72
|
+
|
|
73
|
+
@patch.object(CONFIG, "ENVIRONMENT", "robocloud")
|
|
74
|
+
def test_report_error_failure(self):
|
|
75
|
+
"""Test report_error function when Jira API fails (original test intention)."""
|
|
76
|
+
with patch.object(
|
|
77
|
+
self.bug_catcher, "_BugCatcher__configurator", new_callable=PropertyMock
|
|
78
|
+
) as mock_configurator:
|
|
79
|
+
patch.object(mock_configurator, "is_jira_configured", return_value=True)
|
|
80
|
+
patch.object(mock_configurator, "is_bugsnag_configured", return_value=True)
|
|
81
|
+
with patch.object(self.bug_catcher._BugCatcher__jira, "report_error") as mock_jira_report:
|
|
82
|
+
mock_jira_report.return_value = False # Simulate API failure
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
raise Exception("Test exception")
|
|
86
|
+
except Exception as ex:
|
|
87
|
+
with patch.object(logger, "warning"):
|
|
88
|
+
self.bug_catcher.report_error(exception=ex)
|
|
89
|
+
mock_jira_report.assert_called_once()
|
|
69
90
|
|
|
70
91
|
|
|
71
92
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|