webhooks.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. from dataclasses import asdict, dataclass
  2. from typing import TYPE_CHECKING, List, Optional
  3. import requests
  4. from .constants import RecipientType
  5. if TYPE_CHECKING:
  6. from issues.models import Issue
  7. from .models import Notification
  8. @dataclass
  9. class WebhookAttachmentField:
  10. title: str
  11. value: str
  12. short: bool
  13. @dataclass
  14. class WebhookAttachment:
  15. title: str
  16. title_link: str
  17. text: str
  18. image_url: Optional[str] = None
  19. color: Optional[str] = None
  20. fields: Optional[List[WebhookAttachmentField]] = None
  21. mrkdown_in: Optional[List[str]] = None
  22. @dataclass
  23. class MSTeamsSection:
  24. """
  25. Similar to WebhookAttachment but for MS Teams
  26. https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL
  27. """
  28. activityTitle: str
  29. activitySubtitle: str
  30. @dataclass
  31. class WebhookPayload:
  32. alias: str
  33. text: str
  34. attachments: List[WebhookAttachment]
  35. sections: List[MSTeamsSection]
  36. def send_webhook(
  37. url: str,
  38. message: str,
  39. attachments: Optional[List[WebhookAttachment]] = None,
  40. sections: Optional[List[MSTeamsSection]] = None,
  41. ):
  42. if not attachments:
  43. attachments = []
  44. if not sections:
  45. sections = []
  46. data = WebhookPayload(
  47. alias="GlitchTip", text=message, attachments=attachments, sections=sections
  48. )
  49. return requests.post(url, json=asdict(data), timeout=10)
  50. def send_issue_as_webhook(url, issues: List["Issue"], issue_count: int = 1):
  51. """
  52. Notification about issues via webhook.
  53. url: Webhook URL
  54. issues: This should be only the issues to send as attachment
  55. issue_count - total issues, may be greater than len(issues)
  56. """
  57. attachments: List[WebhookAttachment] = []
  58. sections: List[MSTeamsSection] = []
  59. for issue in issues:
  60. fields = [
  61. WebhookAttachmentField(
  62. title="Project",
  63. value=issue.project.name,
  64. short=True,
  65. )
  66. ]
  67. environment = issue.tags.get("environment")
  68. if environment:
  69. fields.append(
  70. WebhookAttachmentField(
  71. title="Environment",
  72. value=environment[0],
  73. short=True,
  74. )
  75. )
  76. release = issue.tags.get("release")
  77. if release:
  78. fields.append(
  79. WebhookAttachmentField(
  80. title="Release",
  81. value=release[0],
  82. short=False,
  83. )
  84. )
  85. attachments.append(
  86. WebhookAttachment(
  87. mrkdown_in=["text"],
  88. title=str(issue),
  89. title_link=issue.get_detail_url(),
  90. text=issue.culprit,
  91. color=issue.get_hex_color(),
  92. fields=fields,
  93. )
  94. )
  95. sections.append(
  96. MSTeamsSection(
  97. activityTitle=str(issue),
  98. activitySubtitle=f"[View Issue {issue.short_id_display}]({issue.get_detail_url()})",
  99. )
  100. )
  101. message = "GlitchTip Alert"
  102. if issue_count > 1:
  103. message += f" ({issue_count} issues)"
  104. return send_webhook(url, message, attachments, sections)
  105. @dataclass
  106. class DiscordField:
  107. name: str
  108. value: str
  109. inline: bool = False
  110. @dataclass
  111. class DiscordEmbed:
  112. title: str
  113. description: str
  114. color: int
  115. url: str
  116. fields: List[DiscordField]
  117. @dataclass
  118. class DiscordWebhookPayload:
  119. content: str
  120. embeds: List[DiscordEmbed]
  121. def send_issue_as_discord_webhook(url, issues: List["Issue"], issue_count: int = 1):
  122. embeds: List[DiscordEmbed] = []
  123. for issue in issues:
  124. fields = [
  125. DiscordField(
  126. name="Project",
  127. value=issue.project.name,
  128. inline=True,
  129. )
  130. ]
  131. environment = issue.tags.get("environment")
  132. if environment:
  133. fields.append(
  134. DiscordField(
  135. name="Environment",
  136. value=environment[0],
  137. inline=True,
  138. )
  139. )
  140. release = issue.tags.get("release")
  141. if release:
  142. fields.append(
  143. DiscordField(
  144. name="Release",
  145. value=release[0],
  146. inline=False,
  147. )
  148. )
  149. embeds.append(
  150. DiscordEmbed(
  151. title=str(issue),
  152. description=issue.culprit,
  153. color=int(issue.get_hex_color()[1:], 16)
  154. if issue.get_hex_color() is not None
  155. else None,
  156. url=issue.get_detail_url(),
  157. fields=fields,
  158. )
  159. )
  160. message = "GlitchTip Alert"
  161. if issue_count > 1:
  162. message += f" ({issue_count} issues)"
  163. return send_discord_webhook(url, message, embeds)
  164. def send_discord_webhook(url: str, message: str, embeds: List[DiscordEmbed]):
  165. payload = DiscordWebhookPayload(content=message, embeds=embeds)
  166. return requests.post(url, json=asdict(payload), timeout=10)
  167. def send_webhook_notification(
  168. notification: "Notification", url: str, recipient_type: str
  169. ):
  170. issue_count = notification.issues.count()
  171. issues = notification.issues.all()[:3] # Show no more than three
  172. match recipient_type:
  173. case RecipientType.DISCORD:
  174. send_issue_as_discord_webhook(url, issues, issue_count)
  175. return
  176. case _:
  177. send_issue_as_webhook(url, issues, issue_count)