serializers.py 12 KB

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