t-bug-catcher 0.6.1__tar.gz → 0.6.3__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.
Files changed (26) hide show
  1. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/PKG-INFO +1 -1
  2. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/setup.cfg +1 -1
  3. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/setup.py +1 -1
  4. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/__init__.py +1 -1
  5. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/bug_catcher.py +5 -2
  6. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/config.py +5 -0
  7. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/jira.py +99 -26
  8. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/PKG-INFO +1 -1
  9. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/MANIFEST.in +0 -0
  10. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/README.rst +0 -0
  11. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/pyproject.toml +0 -0
  12. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/requirements.txt +0 -0
  13. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/bug_snag.py +0 -0
  14. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/exceptions.py +0 -0
  15. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/resources/whispers_config.yml +0 -0
  16. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/stack_saver.py +0 -0
  17. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/utils/__init__.py +0 -0
  18. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/utils/common.py +0 -0
  19. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/utils/logger.py +0 -0
  20. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/workitems.py +0 -0
  21. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/SOURCES.txt +0 -0
  22. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
  23. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/not-zip-safe +0 -0
  24. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/requires.txt +0 -0
  25. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/top_level.txt +0 -0
  26. {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/tests/test_t_bug_catcher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: t_bug_catcher
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: Bug catcher
5
5
  Home-page: https://www.thoughtful.ai/
6
6
  Author: Thoughtful
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.6.1
2
+ current_version = 0.6.3
3
3
  commit = True
4
4
  tag = False
5
5
 
@@ -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.1",
29
+ version="0.6.3",
30
30
  zip_safe=False,
31
31
  install_requires=install_requirements,
32
32
  include_package_data=True,
@@ -3,7 +3,7 @@
3
3
  __author__ = """Thoughtful"""
4
4
  __email__ = "support@thoughtful.ai"
5
5
  # fmt: off
6
- __version__ = '0.6.1'
6
+ __version__ = '0.6.3'
7
7
  # fmt: on
8
8
 
9
9
  from .bug_catcher import (
@@ -111,6 +111,7 @@ class BugCatcher:
111
111
  attachments: Optional[List] = None,
112
112
  assignee: Optional[str] = None,
113
113
  team: Optional[str] = None,
114
+ group_by: Optional[str] = None,
114
115
  ):
115
116
  """Reports an error to the Jira project.
116
117
 
@@ -121,6 +122,7 @@ class BugCatcher:
121
122
  attachments (List, optional): The attachments to be added to the Jira issue. Defaults to None.
122
123
  assignee (str, optional): The assignee to be added to the Jira issue. Defaults to None.
123
124
  team (str, optional): The team to be assigned to the Jira issue. Defaults to None.
125
+ group_by (str, optional): The group to be assigned to the Jira issue. Defaults to None.
124
126
 
125
127
  Returns:
126
128
  None
@@ -182,6 +184,7 @@ class BugCatcher:
182
184
  stack_trace=stack_trace,
183
185
  additional_info=description,
184
186
  metadata=metadata,
187
+ group_by=group_by,
185
188
  )
186
189
 
187
190
  if self.__configurator.is_bugsnag_configured:
@@ -271,7 +274,7 @@ class BugCatcher:
271
274
  Args:
272
275
  exc_type (type): The type of the exception.
273
276
  exc_value (Exception): The value of the exception.
274
- traceback (traceback): The traceback of the exception.
277
+ exc_traceback (traceback): The traceback of the exception.
275
278
 
276
279
  Returns:
277
280
  None
@@ -292,7 +295,7 @@ class BugCatcher:
292
295
  stack_trace = self.__stack_saver.save_stack_trace(exc_value)
293
296
 
294
297
  if self.__configurator.is_jira_configured:
295
- self.__jira.report_unhandled_error(exc_type, exc_value, exc_traceback, stack_trace)
298
+ self.__jira.report_unhandled_error(exc_value, stack_trace)
296
299
  if self.__configurator.is_bugsnag_configured:
297
300
  self.__bug_snag.report_unhandled_error(exc_type, exc_value, exc_traceback)
298
301
  frames = get_frames(exc_value.__traceback__)
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import socket
2
3
 
3
4
  from .workitems import metadata, variables
4
5
 
@@ -16,6 +17,7 @@ class Config:
16
17
  STACK_SCOPE: int = 3
17
18
  STACK_ITEM_LENGTH: int = 100
18
19
  STACK_TEXT_LENGTH: int = 10000
20
+ COMMENT_LIMIT: int = 4950
19
21
 
20
22
  class TICKET_PRIORITIES:
21
23
  """Priorities class for configuring the application."""
@@ -32,6 +34,9 @@ class Config:
32
34
  KEYS_TO_REMOVE = ["credential", "password"]
33
35
  BUILD_INFO_FILE = "commit_info.json"
34
36
 
37
+ HOST = socket.gethostname() if os.name.lower() == "nt" else None
38
+ UNAME = os.getlogin() if os.name.lower() == "nt" else None
39
+
35
40
  RC_RUN_LINK = (
36
41
  f"https://cloud.robocorp.com/organizations/{os.environ.get('RC_ORGANIZATION_ID')}"
37
42
  f"/workspaces/{os.environ.get('RC_WORKSPACE_ID')}/processes"
@@ -7,6 +7,7 @@ import os
7
7
  import re
8
8
  import sys
9
9
  import traceback
10
+ import zlib
10
11
  from datetime import datetime
11
12
  from importlib.metadata import version
12
13
  from pathlib import Path
@@ -643,6 +644,34 @@ class Jira:
643
644
  else []
644
645
  )
645
646
 
647
+ @staticmethod
648
+ def __host_markup() -> List[dict]:
649
+ """Create the host worker markup.
650
+
651
+ Returns:
652
+ dict: The host worker markup.
653
+ """
654
+ return (
655
+ [
656
+ {
657
+ "type": "paragraph",
658
+ "content": [
659
+ {
660
+ "type": "text",
661
+ "text": "Host: ",
662
+ "marks": [{"type": "strong"}],
663
+ },
664
+ {
665
+ "type": "text",
666
+ "text": f"{CONFIG.HOST} > {CONFIG.UNAME}",
667
+ },
668
+ ],
669
+ }
670
+ ]
671
+ if CONFIG.HOST and CONFIG.UNAME
672
+ else []
673
+ )
674
+
646
675
  @staticmethod
647
676
  def __traceback_markup(exc_traceback_info: str) -> List[dict]:
648
677
  """Create the traceback markup.
@@ -837,6 +866,7 @@ class Jira:
837
866
  + self.__date_markup()
838
867
  + self.__runlink_markup()
839
868
  + self.__environment_markup()
869
+ + self.__host_markup()
840
870
  + self.__branch_info_markup(exc_traceback)
841
871
  + (self.__description_markup(additional_info) if additional_info else [])
842
872
  + self.__traceback_markup(exc_traceback_info)
@@ -1125,9 +1155,10 @@ class Jira:
1125
1155
  else []
1126
1156
  )
1127
1157
 
1128
- if len(issue.get("fields", {}).get("comment", [])) >= 5000:
1158
+ if len(issue.get("fields", {}).get("comment", [])) >= CONFIG.LIMITS.COMMENT_LIMIT:
1129
1159
  logger.warning(
1130
- f"Comments for '{issue.get('key')}' were not posted due to exceeding JIRA comments limit (5000)."
1160
+ f"Comments for '{issue.get('key')}' were not posted due to exceeding JIRA comments limit "
1161
+ f"({CONFIG.LIMITS.COMMENT_LIMIT})."
1131
1162
  )
1132
1163
  return
1133
1164
 
@@ -1282,6 +1313,7 @@ class Jira:
1282
1313
  stack_trace: Optional[str] = None,
1283
1314
  metadata: Optional[dict] = None,
1284
1315
  additional_info: Optional[str] = None,
1316
+ group_by: Optional[str] = None,
1285
1317
  ) -> dict:
1286
1318
  """Create a Jira issue with the given attachments.
1287
1319
 
@@ -1293,6 +1325,7 @@ class Jira:
1293
1325
  stack_trace (str, optional): Stack trace to be added to the Jira issue.
1294
1326
  metadata (dict, optional): Metadata to be added to the Jira issue.
1295
1327
  additional_info (str, optional): Additional information to be added to the Jira issue.
1328
+ group_by (str, optional): group_by flag to be added to the Jira issue.
1296
1329
 
1297
1330
  Returns:
1298
1331
  The response from creating the Jira issue.
@@ -1318,13 +1351,32 @@ class Jira:
1318
1351
  logger.warning(f"Only the first {CONFIG.LIMITS.MAX_ATTACHMENTS} attachments were uploaded.")
1319
1352
  attachments = attachments[: CONFIG.LIMITS.MAX_ATTACHMENTS]
1320
1353
 
1321
- summary = self.__create_summary(type(exception), exception, exception.__traceback__)
1322
- error_id = self.__generate_error_id(exc_type=type(exception), exc_traceback=exception.__traceback__)
1354
+ error_id = self.__generate_error_id(exception)
1355
+ group_id = self.generate_group_id(group_by)
1356
+
1357
+ all_issues = self.get_issues()["issues"]
1358
+
1359
+ if group_id:
1360
+ existing_tickets = [
1361
+ ticket
1362
+ for ticket in all_issues
1363
+ if ticket["fields"]["description"] and f"{error_id}-" in ticket["fields"]["description"]
1364
+ ]
1365
+ summary = self.__create_summary(
1366
+ type(exception),
1367
+ exception,
1368
+ exception.__traceback__,
1369
+ len(existing_tickets) + 1 if existing_tickets else 1,
1370
+ )
1371
+ error_id = f"{error_id}-{group_id}"
1372
+ else:
1373
+ summary = self.__create_summary(type(exception), exception, exception.__traceback__)
1323
1374
 
1324
1375
  existing_ticket = self.filter_tickets(
1325
- all_tickets=self.get_issues()["issues"],
1376
+ all_tickets=all_issues,
1326
1377
  error_id=error_id,
1327
1378
  )
1379
+
1328
1380
  if existing_ticket:
1329
1381
  self.__update_existing_ticket(
1330
1382
  existing_ticket=existing_ticket,
@@ -1376,23 +1428,20 @@ class Jira:
1376
1428
  self.report_internal_error(exception=ex, additional_info="Failed to report error.")
1377
1429
  return False
1378
1430
 
1379
- def report_unhandled_error(
1380
- self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType, stack_trace: str = None
1381
- ):
1431
+ def report_unhandled_error(self, exception: Exception, stack_trace: str = None):
1382
1432
  """Report an unhandled error to Jira.
1383
1433
 
1384
1434
  Args:
1385
1435
  exc_type (type): The type of the exception.
1386
- exc_value (Exception, str): The value of the exception.
1387
- exc_traceback (TracebackType): The traceback of the exception.
1436
+ exception (Exception, str): The value of the exception.
1388
1437
  stack_trace (str, optional): Stack trace to be added to the Jira issue.
1389
1438
 
1390
1439
  Returns:
1391
1440
  The response from creating the Jira issue.
1392
1441
  """
1393
1442
  try:
1394
- summary = self.__create_summary(exc_type, exc_value, exc_traceback)
1395
- error_id = self.__generate_error_id(exc_type=exc_type, exc_traceback=exc_traceback)
1443
+ summary = self.__create_summary(type(exception), exception, exception.__traceback__)
1444
+ error_id = self.__generate_error_id(exception)
1396
1445
 
1397
1446
  existing_ticket = self.filter_tickets(
1398
1447
  all_tickets=self.get_issues()["issues"],
@@ -1415,9 +1464,9 @@ class Jira:
1415
1464
  logger.info(f"Failed to get assignee {self._default_assignee} due to: {ex}")
1416
1465
 
1417
1466
  description = self.__create_description_markup(
1418
- exc_type=exc_type,
1419
- exc_value=exc_value,
1420
- exc_traceback=exc_traceback,
1467
+ exc_type=type(exception),
1468
+ exc_value=exception,
1469
+ exc_traceback=exception.__traceback__,
1421
1470
  error_id=error_id,
1422
1471
  )
1423
1472
 
@@ -1444,6 +1493,8 @@ class Jira:
1444
1493
 
1445
1494
  Args:
1446
1495
  exception (Exception): The exception to be added to the Jira issue.
1496
+ metadata (dict, optional): The metadata to be added to the Jira issue. Defaults to None.
1497
+ additional_info (str, optional): Additional information to be added to the Jira issue. Defaults to None.
1447
1498
 
1448
1499
  Returns:
1449
1500
  The response from creating the Jira issue.
@@ -1455,7 +1506,7 @@ class Jira:
1455
1506
  attachments = [str(file) for file in Path().cwd().glob("stack_details_*.json")]
1456
1507
 
1457
1508
  summary = self.__create_summary(type(exception), exception, exception.__traceback__)
1458
- error_id = self.__generate_error_id(exc_type=type(exception), exc_traceback=exception.__traceback__)
1509
+ error_id = self.__generate_error_id(exception)
1459
1510
 
1460
1511
  existing_ticket = self.filter_tickets(
1461
1512
  all_tickets=self.get_issues(project_key=CONFIG.BC_BOARD)["issues"],
@@ -1623,31 +1674,47 @@ class Jira:
1623
1674
  for ticket in all_tickets:
1624
1675
  if not ticket["fields"]["description"]:
1625
1676
  continue
1626
- if error_id not in ticket["fields"]["description"]:
1677
+ if f"{error_id}_~" not in ticket["fields"]["description"]:
1627
1678
  continue
1628
1679
  return ticket
1629
1680
 
1630
1681
  else:
1631
1682
  return None
1632
1683
 
1633
- def __generate_error_id(self, exc_type: type, exc_traceback: TracebackType) -> str:
1684
+ def generate_group_id(self, group_by: Optional[str] = None) -> Optional[str]:
1685
+ """Generates a group ID based on the provided group_by string.
1686
+
1687
+ Args:
1688
+ group_by (str): The string to use for generating the group ID.
1689
+
1690
+ Returns:
1691
+ str or None: The generated group ID if group_by is not None, otherwise None.
1692
+ """
1693
+ if not group_by:
1694
+ return None
1695
+ crc_hash = zlib.crc32(group_by.encode())
1696
+ return format(crc_hash, "x")
1697
+
1698
+ def __generate_error_id(self, exception: Exception) -> str:
1634
1699
  """Generates an error string ID using the exception, function name, and error string.
1635
1700
 
1636
1701
  Args:
1637
- exc_type (type): The type of the exception.
1638
- exc_traceback (TracebackType): The traceback of the exception.
1702
+ exception (Exception):
1639
1703
 
1640
1704
  Returns:
1641
- str: The generated error string ID.
1705
+ str: The generated error string ID and the group ID.
1642
1706
 
1643
1707
  """
1644
- frames = get_frames(exc_traceback)
1708
+ frames = get_frames(exception.__traceback__)
1645
1709
  exception_chain = "-".join([f"{frame.name}" for frame in frames])
1646
1710
  rel_path = os.path.relpath(frames[-1].filename, os.getcwd())
1647
1711
  path = Path(os.path.splitext(rel_path)[0]).as_posix()
1648
1712
  path = remove_holotree_id(path)
1649
- error_id = f"{path}-{exception_chain}-{frames[-1].line}-{exc_type.__module__}-{exc_type.__name__}"
1650
- return hashlib.md5(error_id.encode()).hexdigest()
1713
+ error_id = (
1714
+ f"{path}-{exception_chain}-{frames[-1].line}-" f"{type(exception).__module__}-{type(exception).__name__}"
1715
+ )
1716
+ hashed_id = hashlib.md5(error_id.encode()).hexdigest()
1717
+ return hashed_id
1651
1718
 
1652
1719
  @staticmethod
1653
1720
  def __generate_warning_id(inspected_frame) -> tuple:
@@ -1700,7 +1767,9 @@ class Jira:
1700
1767
  return str(message)
1701
1768
  return re.sub(r"\'(.+)\'", "'...'", message)
1702
1769
 
1703
- def __create_summary(self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType) -> str:
1770
+ def __create_summary(
1771
+ self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType, idx: Optional[int] = None
1772
+ ) -> str:
1704
1773
  """Create the summary of the ticket.
1705
1774
 
1706
1775
  Args:
@@ -1713,7 +1782,11 @@ class Jira:
1713
1782
  """
1714
1783
  frames = get_frames(exc_traceback)
1715
1784
  file_name, line_no, _, _ = frames[-1]
1716
- summary = f"[{exc_type.__name__}:{os.path.basename(file_name)}:{line_no}]"
1785
+
1786
+ if idx:
1787
+ summary = f"[{exc_type.__name__}:{os.path.basename(file_name)}:{line_no}({idx})]"
1788
+ else:
1789
+ summary = f"[{exc_type.__name__}:{os.path.basename(file_name)}:{line_no}]"
1717
1790
  if self._project_key == CONFIG.SUPPORT_BOARD and CONFIG.ADMIN_CODE:
1718
1791
  summary = CONFIG.ADMIN_CODE + " - " + summary
1719
1792
  if CONFIG.LIMITS.SUMMARY_LENGTH <= len(summary):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: t_bug_catcher
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: Bug catcher
5
5
  Home-page: https://www.thoughtful.ai/
6
6
  Author: Thoughtful
File without changes
File without changes