models.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import uuid
  2. from django.contrib.postgres.indexes import GinIndex
  3. from django.contrib.postgres.search import SearchVectorField
  4. from django.db import models
  5. from psqlextra.models import PostgresPartitionedModel
  6. from psqlextra.types import PostgresPartitioningMethod
  7. from sentry.constants import MAX_CULPRIT_LENGTH
  8. from .constants import EventStatus, IssueEventType, LogLevel
  9. class Issue(models.Model):
  10. created = models.DateTimeField(auto_now_add=True)
  11. culprit = models.CharField(max_length=1024, blank=True, null=True)
  12. is_public = models.BooleanField(default=False)
  13. level = models.PositiveSmallIntegerField(
  14. choices=LogLevel.choices, default=LogLevel.ERROR
  15. )
  16. metadata = models.JSONField()
  17. project = models.ForeignKey(
  18. "projects.Project", on_delete=models.CASCADE, related_name="issues"
  19. )
  20. title = models.CharField(max_length=255)
  21. type = models.PositiveSmallIntegerField(
  22. choices=IssueEventType.choices, default=IssueEventType.DEFAULT
  23. )
  24. status = models.PositiveSmallIntegerField(
  25. choices=EventStatus.choices, default=EventStatus.UNRESOLVED
  26. )
  27. short_id = models.PositiveIntegerField(null=True)
  28. class Meta:
  29. unique_together = (("project", "short_id"),)
  30. class IssueStats(models.Model):
  31. issue = models.OneToOneField(Issue, primary_key=True, on_delete=models.CASCADE)
  32. search_vector = SearchVectorField(null=True, editable=False)
  33. search_vector_created = models.DateTimeField(auto_now_add=True)
  34. count = models.PositiveIntegerField(default=1, editable=False)
  35. last_seen = models.DateTimeField(auto_now_add=True, db_index=True)
  36. class Meta:
  37. indexes = [GinIndex(fields=["search_vector"])]
  38. class IssueHash(models.Model):
  39. issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name="hashes")
  40. # Redundant project allows for unique constraint
  41. project = models.ForeignKey(
  42. "projects.Project", on_delete=models.CASCADE, related_name="+"
  43. )
  44. value = models.UUIDField(db_index=True)
  45. class Meta:
  46. constraints = [
  47. models.UniqueConstraint(
  48. fields=["project", "value"], name="issue hash project"
  49. )
  50. ]
  51. class Comment(models.Model):
  52. created = models.DateTimeField(auto_now_add=True)
  53. issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name="comments")
  54. user = models.ForeignKey(
  55. "users.User", null=True, on_delete=models.SET_NULL, related_name="+"
  56. )
  57. text = models.TextField(blank=True, null=True)
  58. class Meta:
  59. ordering = ("-created",)
  60. class IssueEvent(PostgresPartitionedModel, models.Model):
  61. id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  62. issue = models.ForeignKey(Issue, on_delete=models.CASCADE)
  63. type = models.PositiveSmallIntegerField(default=0, choices=IssueEventType.choices)
  64. timestamp = models.DateTimeField(help_text="Time at which event happened")
  65. received = models.DateTimeField(help_text="Time at which GlitchTip accepted event")
  66. title = models.CharField(max_length=255)
  67. transaction = models.CharField(max_length=MAX_CULPRIT_LENGTH)
  68. level = models.PositiveSmallIntegerField(
  69. choices=LogLevel.choices, default=LogLevel.ERROR
  70. )
  71. data = models.JSONField()
  72. # This could be HStore, but jsonb is just as good and removes need for
  73. # 'django.contrib.postgres' which makes several unnecessary SQL calls
  74. tags = models.JSONField()
  75. class PartitioningMeta:
  76. method = PostgresPartitioningMethod.RANGE
  77. key = ["received"]
  78. def __str__(self):
  79. return self.eventID
  80. @property
  81. def eventID(self):
  82. return self.id.hex
  83. @property
  84. def message(self):
  85. """Often the title and message are the same. If message isn't stored, assume it's the title"""
  86. return self.data.get("message", self.title)
  87. @property
  88. def metadata(self):
  89. """Return metadata if exists, else return just the title as metadata"""
  90. return self.data.get("metadata", {"title": self.title})
  91. @property
  92. def platform(self):
  93. return self.data.get("platform")