serializers.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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. request = instance.get("request")
  77. if request:
  78. request["inferredContentType"] = request.pop("inferred_content_type", None)
  79. entries.append({"type": "request", "data": request})
  80. message = instance.get("message")
  81. if message:
  82. entries.append({"type": "message", "data": {"formatted": message}})
  83. csp = instance.get("csp")
  84. if csp:
  85. entries.append({"type": EventType.CSP.label, "data": csp})
  86. return entries
  87. class EventTagField(serializers.HStoreField):
  88. def to_representation(self, obj):
  89. return [{"key": tag[0], "value": tag[1]} for tag in obj.items()]
  90. class EventSerializer(serializers.ModelSerializer):
  91. eventID = serializers.CharField(source="event_id_hex")
  92. id = serializers.CharField(source="event_id_hex")
  93. dateCreated = serializers.DateTimeField(source="timestamp")
  94. dateReceived = serializers.DateTimeField(source="created")
  95. entries = EventEntriesSerializer(source="data")
  96. tags = EventTagField()
  97. user = EventUserSerializer()
  98. class Meta:
  99. model = Event
  100. fields = (
  101. "eventID",
  102. "id",
  103. "issue",
  104. "context",
  105. "contexts",
  106. "culprit",
  107. "dateCreated",
  108. "dateReceived",
  109. "entries",
  110. # "errors",
  111. # "location",
  112. "message",
  113. "metadata",
  114. "packages",
  115. "platform",
  116. "sdk",
  117. "tags",
  118. "title",
  119. "type",
  120. "user",
  121. )
  122. class EventDetailSerializer(EventSerializer):
  123. projectID = serializers.IntegerField(source="issue.project_id")
  124. userReport = UserReportSerializer(source="user_report")
  125. nextEventID = serializers.SerializerMethodField()
  126. previousEventID = serializers.SerializerMethodField()
  127. release = ReleaseSerializer()
  128. class Meta(EventSerializer.Meta):
  129. fields = EventSerializer.Meta.fields + (
  130. "projectID",
  131. "userReport",
  132. "nextEventID",
  133. "previousEventID",
  134. "release",
  135. )
  136. def get_next_or_previous(self, obj, is_next):
  137. kwargs = self.context["view"].kwargs
  138. filter_kwargs = {}
  139. if kwargs.get("issue_pk"):
  140. filter_kwargs["issue"] = kwargs["issue_pk"]
  141. if is_next:
  142. result = obj.next(**filter_kwargs)
  143. else:
  144. result = obj.previous(**filter_kwargs)
  145. if result:
  146. return str(result)
  147. def get_nextEventID(self, obj):
  148. return self.get_next_or_previous(obj, True)
  149. def get_previousEventID(self, obj):
  150. return self.get_next_or_previous(obj, False)
  151. class DisplayChoiceField(serializers.ChoiceField):
  152. """
  153. ChoiceField that represents choice only as display value
  154. Useful if the API should only deal with display values
  155. """
  156. def to_representation(self, value):
  157. return self.choices[value]
  158. def to_internal_value(self, data):
  159. if data == "" and self.allow_blank:
  160. return ""
  161. choice_strings_to_values = {value: key for key, value in self.choices.items()}
  162. try:
  163. return choice_strings_to_values[str(data)]
  164. except KeyError:
  165. self.fail("invalid_choice", input=data)
  166. class IssueSerializer(serializers.ModelSerializer):
  167. annotations = serializers.JSONField(default=list, read_only=True)
  168. assignedTo = serializers.CharField(default=None, read_only=True)
  169. count = serializers.CharField(read_only=True)
  170. firstSeen = serializers.DateTimeField(source="created", read_only=True)
  171. hasSeen = serializers.BooleanField(source="has_seen", read_only=True)
  172. id = serializers.CharField(read_only=True)
  173. isBookmarked = serializers.BooleanField(default=False, read_only=True)
  174. isPublic = serializers.BooleanField(source="is_public", read_only=True)
  175. isSubscribed = serializers.BooleanField(default=False, read_only=True)
  176. lastSeen = serializers.DateTimeField(source="last_seen", read_only=True)
  177. level = serializers.CharField(source="get_level_display", read_only=True)
  178. logger = serializers.CharField(default=None, read_only=True)
  179. metadata = serializers.JSONField(default=dict, read_only=True)
  180. numComments = serializers.IntegerField(default=0, read_only=True)
  181. permalink = serializers.CharField(default="Not implemented", read_only=True)
  182. project = ProjectReferenceSerializer(read_only=True)
  183. shareId = serializers.IntegerField(default=None, read_only=True)
  184. shortId = serializers.CharField(source="short_id_display", read_only=True)
  185. stats = serializers.JSONField(default=dict, read_only=True)
  186. status = DisplayChoiceField(choices=EventStatus.choices)
  187. statusDetails = serializers.JSONField(default=dict, read_only=True)
  188. subscriptionDetails = serializers.CharField(default=None, read_only=True)
  189. type = serializers.CharField(source="get_type_display", read_only=True)
  190. userReportCount = serializers.IntegerField(
  191. source="userreport_set.count", read_only=True
  192. )
  193. userCount = serializers.IntegerField(default=0, read_only=True)
  194. matchingEventId = serializers.SerializerMethodField()
  195. class Meta:
  196. model = Issue
  197. fields = (
  198. "annotations",
  199. "assignedTo",
  200. "count",
  201. "culprit",
  202. "firstSeen",
  203. "hasSeen",
  204. "id",
  205. "isBookmarked",
  206. "isPublic",
  207. "isSubscribed",
  208. "lastSeen",
  209. "level",
  210. "logger",
  211. "metadata",
  212. "numComments",
  213. "permalink",
  214. "project",
  215. "shareId",
  216. "shortId",
  217. "stats",
  218. "status",
  219. "statusDetails",
  220. "subscriptionDetails",
  221. "title",
  222. "type",
  223. "userReportCount",
  224. "userCount",
  225. "matchingEventId",
  226. )
  227. read_only_fields = (
  228. "annotations",
  229. "assignedTo",
  230. "count",
  231. "culprit",
  232. "firstSeen",
  233. "hasSeen",
  234. "id",
  235. "isBookmarked",
  236. "isPublic",
  237. "isSubscribed",
  238. "lastSeen",
  239. "level",
  240. "logger",
  241. "metadata",
  242. "numComments",
  243. "permalink",
  244. "project",
  245. "shareId",
  246. "shortId",
  247. "stats",
  248. "subscriptionDetails",
  249. "title",
  250. "type",
  251. "userCount",
  252. )
  253. def to_representation(self, obj):
  254. """ Workaround for "type" and "matchingEventId" fields """
  255. primitive_repr = super().to_representation(obj)
  256. primitive_repr["type"] = obj.get_type_display()
  257. if primitive_repr["matchingEventId"] is None:
  258. del primitive_repr["matchingEventId"]
  259. return primitive_repr
  260. def get_matchingEventId(self, obj):
  261. matching_event_id = self.context.get("matching_event_id")
  262. if matching_event_id:
  263. return matching_event_id
  264. return None