serializers.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. from rest_framework import serializers
  2. from projects.serializers.base_serializers import ProjectReferenceSerializer
  3. from user_reports.serializers import UserReportSerializer
  4. from sentry.interfaces.stacktrace import get_context
  5. from glitchtip.serializers import FlexibleDateTimeField
  6. from releases.serializers import ReleaseSerializer
  7. from events.models import Event
  8. from .models import Issue, EventType, EventStatus
  9. class EventUserSerializer(serializers.Serializer):
  10. username = serializers.CharField(allow_null=True)
  11. name = serializers.CharField(allow_null=True)
  12. ip_address = serializers.IPAddressField(allow_null=True)
  13. email = serializers.EmailField(allow_null=True)
  14. data = serializers.JSONField(default={})
  15. id = serializers.CharField(allow_null=True)
  16. class BaseBreadcrumbsSerializer(serializers.Serializer):
  17. category = serializers.CharField()
  18. level = serializers.CharField(default="info")
  19. event_id = serializers.CharField(required=False)
  20. data = serializers.JSONField(required=False)
  21. message = serializers.CharField(required=False)
  22. type = serializers.CharField(default="default")
  23. class BreadcrumbsSerializer(BaseBreadcrumbsSerializer):
  24. timestamp = FlexibleDateTimeField()
  25. message = serializers.CharField(default=None)
  26. event_id = serializers.CharField(default=None)
  27. data = serializers.JSONField(default=None)
  28. class EventEntriesSerializer(serializers.Serializer):
  29. def to_representation(self, instance):
  30. def get_has_system_frames(frames):
  31. return any(frame.in_app for frame in frames)
  32. entries = []
  33. exception = instance.get("exception")
  34. # Some, but not all, keys are made more JS camel case like
  35. if exception and exception.get("values"):
  36. # https://gitlab.com/glitchtip/sentry-open-source/sentry/-/blob/master/src/sentry/interfaces/stacktrace.py#L487
  37. # if any frame is "in_app" set this to True
  38. exception["hasSystemFrames"] = False
  39. for value in exception["values"]:
  40. if (
  41. value.get("stacktrace", None) is not None
  42. and "frames" in value["stacktrace"]
  43. ):
  44. for frame in value["stacktrace"]["frames"]:
  45. if frame.get("in_app") == True:
  46. exception["hasSystemFrames"] = True
  47. if "in_app" in frame:
  48. frame["inApp"] = frame.pop("in_app")
  49. if "abs_path" in frame:
  50. frame["absPath"] = frame.pop("abs_path")
  51. if "colno" in frame:
  52. frame["colNo"] = frame.pop("colno")
  53. if "lineno" in frame:
  54. frame["lineNo"] = frame.pop("lineno")
  55. pre_context = frame.pop("pre_context", None)
  56. post_context = frame.pop("post_context", None)
  57. frame["context"] = get_context(
  58. frame["lineNo"],
  59. frame.get("context_line"),
  60. pre_context,
  61. post_context,
  62. )
  63. entries.append({"type": "exception", "data": exception})
  64. breadcrumbs = instance.get("breadcrumbs")
  65. if breadcrumbs:
  66. breadcrumbs_serializer = BreadcrumbsSerializer(
  67. data=breadcrumbs.get("values"), many=True
  68. )
  69. if breadcrumbs_serializer.is_valid():
  70. entries.append(
  71. {
  72. "type": "breadcrumbs",
  73. "data": {"values": breadcrumbs_serializer.validated_data},
  74. }
  75. )
  76. logentry = instance.get("logentry")
  77. message = instance.get("message")
  78. if logentry:
  79. entries.append({"type": "message", "data": logentry})
  80. elif message:
  81. entries.append({"type": "message", "data": {"formatted": message}})
  82. request = instance.get("request")
  83. if request:
  84. request["inferredContentType"] = request.pop("inferred_content_type", None)
  85. entries.append({"type": "request", "data": request})
  86. csp = instance.get("csp")
  87. if csp:
  88. entries.append({"type": EventType.CSP.label, "data": csp})
  89. return entries
  90. class EventTagField(serializers.HStoreField):
  91. def to_representation(self, obj):
  92. return [{"key": tag[0], "value": tag[1]} for tag in obj.items()]
  93. class EventSerializer(serializers.ModelSerializer):
  94. eventID = serializers.CharField(source="event_id_hex")
  95. id = serializers.CharField(source="event_id_hex")
  96. dateCreated = serializers.DateTimeField(source="timestamp")
  97. dateReceived = serializers.DateTimeField(source="created")
  98. entries = EventEntriesSerializer(source="data")
  99. tags = EventTagField()
  100. user = EventUserSerializer()
  101. class Meta:
  102. model = Event
  103. fields = (
  104. "eventID",
  105. "id",
  106. "issue",
  107. "context",
  108. "contexts",
  109. "culprit",
  110. "dateCreated",
  111. "dateReceived",
  112. "entries",
  113. # "errors",
  114. # "location",
  115. "message",
  116. "metadata",
  117. "packages",
  118. "platform",
  119. "sdk",
  120. "tags",
  121. "title",
  122. "type",
  123. "user",
  124. )
  125. class EventDetailSerializer(EventSerializer):
  126. projectID = serializers.IntegerField(source="issue.project_id")
  127. userReport = UserReportSerializer(source="user_report")
  128. nextEventID = serializers.SerializerMethodField()
  129. previousEventID = serializers.SerializerMethodField()
  130. release = ReleaseSerializer()
  131. class Meta(EventSerializer.Meta):
  132. fields = EventSerializer.Meta.fields + (
  133. "projectID",
  134. "userReport",
  135. "nextEventID",
  136. "previousEventID",
  137. "release",
  138. )
  139. def get_next_or_previous(self, obj, is_next):
  140. kwargs = self.context["view"].kwargs
  141. filter_kwargs = {}
  142. if kwargs.get("issue_pk"):
  143. filter_kwargs["issue"] = kwargs["issue_pk"]
  144. if is_next:
  145. result = obj.next(**filter_kwargs)
  146. else:
  147. result = obj.previous(**filter_kwargs)
  148. if result:
  149. return str(result)
  150. def get_nextEventID(self, obj):
  151. return self.get_next_or_previous(obj, True)
  152. def get_previousEventID(self, obj):
  153. return self.get_next_or_previous(obj, False)
  154. class DisplayChoiceField(serializers.ChoiceField):
  155. """
  156. ChoiceField that represents choice only as display value
  157. Useful if the API should only deal with display values
  158. """
  159. def to_representation(self, value):
  160. return self.choices[value]
  161. def to_internal_value(self, data):
  162. if data == "" and self.allow_blank:
  163. return ""
  164. choice_strings_to_values = {value: key for key, value in self.choices.items()}
  165. try:
  166. return choice_strings_to_values[str(data)]
  167. except KeyError:
  168. self.fail("invalid_choice", input=data)
  169. class IssueSerializer(serializers.ModelSerializer):
  170. annotations = serializers.JSONField(default=list, read_only=True)
  171. assignedTo = serializers.CharField(default=None, read_only=True)
  172. count = serializers.CharField(read_only=True)
  173. firstSeen = serializers.DateTimeField(source="created", read_only=True)
  174. hasSeen = serializers.BooleanField(source="has_seen", read_only=True)
  175. id = serializers.CharField(read_only=True)
  176. isBookmarked = serializers.BooleanField(default=False, read_only=True)
  177. isPublic = serializers.BooleanField(source="is_public", read_only=True)
  178. isSubscribed = serializers.BooleanField(default=False, read_only=True)
  179. lastSeen = serializers.DateTimeField(source="last_seen", read_only=True)
  180. level = serializers.CharField(source="get_level_display", read_only=True)
  181. logger = serializers.CharField(default=None, read_only=True)
  182. metadata = serializers.JSONField(default=dict, read_only=True)
  183. numComments = serializers.IntegerField(default=0, read_only=True)
  184. permalink = serializers.CharField(default="Not implemented", read_only=True)
  185. project = ProjectReferenceSerializer(read_only=True)
  186. shareId = serializers.IntegerField(default=None, read_only=True)
  187. shortId = serializers.CharField(source="short_id_display", read_only=True)
  188. stats = serializers.JSONField(default=dict, read_only=True)
  189. status = DisplayChoiceField(choices=EventStatus.choices)
  190. statusDetails = serializers.JSONField(default=dict, read_only=True)
  191. subscriptionDetails = serializers.CharField(default=None, read_only=True)
  192. type = serializers.CharField(source="get_type_display", read_only=True)
  193. userReportCount = serializers.IntegerField(
  194. source="userreport_set.count", read_only=True
  195. )
  196. userCount = serializers.IntegerField(default=0, read_only=True)
  197. matchingEventId = serializers.SerializerMethodField()
  198. class Meta:
  199. model = Issue
  200. fields = (
  201. "annotations",
  202. "assignedTo",
  203. "count",
  204. "culprit",
  205. "firstSeen",
  206. "hasSeen",
  207. "id",
  208. "isBookmarked",
  209. "isPublic",
  210. "isSubscribed",
  211. "lastSeen",
  212. "level",
  213. "logger",
  214. "metadata",
  215. "numComments",
  216. "permalink",
  217. "project",
  218. "shareId",
  219. "shortId",
  220. "stats",
  221. "status",
  222. "statusDetails",
  223. "subscriptionDetails",
  224. "title",
  225. "type",
  226. "userReportCount",
  227. "userCount",
  228. "matchingEventId",
  229. )
  230. read_only_fields = (
  231. "annotations",
  232. "assignedTo",
  233. "count",
  234. "culprit",
  235. "firstSeen",
  236. "hasSeen",
  237. "id",
  238. "isBookmarked",
  239. "isPublic",
  240. "isSubscribed",
  241. "lastSeen",
  242. "level",
  243. "logger",
  244. "metadata",
  245. "numComments",
  246. "permalink",
  247. "project",
  248. "shareId",
  249. "shortId",
  250. "stats",
  251. "subscriptionDetails",
  252. "title",
  253. "type",
  254. "userCount",
  255. )
  256. def to_representation(self, obj):
  257. """ Workaround for "type" and "matchingEventId" fields """
  258. primitive_repr = super().to_representation(obj)
  259. primitive_repr["type"] = obj.get_type_display()
  260. if primitive_repr["matchingEventId"] is None:
  261. del primitive_repr["matchingEventId"]
  262. return primitive_repr
  263. def get_matchingEventId(self, obj):
  264. matching_event_id = self.context.get("matching_event_id")
  265. if matching_event_id:
  266. return matching_event_id
  267. return None