Remove more of old issues and events
This commit is contained in:
1 parent
48095e25b5
commit
e915961d5b
9 files changed
+390
-313
No files matched your search
-1
@@ -14,7 +14,6 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("issues", "0001_squashed_0010_auto_20210117_1543"),
|
||||
(
|
||||
"projects",
|
||||
"0001_squashed_0009_alter_project_id_alter_projectcounter_id_and_more",
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# Generated by Django 5.0.3 on 2024-03-15 14:05
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
replaces = [
|
||||
(
|
||||
"alerts",
|
||||
"0001_squashed_0007_alter_alertrecipient_id_alter_notification_id_and_more",
|
||||
),
|
||||
("alerts", "0008_alter_alertrecipient_recipient_type"),
|
||||
("alerts", "0009_alter_alertrecipient_recipient_type"),
|
||||
("alerts", "0010_alter_notification_issues"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("issue_events", "0001_initial"),
|
||||
(
|
||||
"projects",
|
||||
"0001_squashed_0009_alter_project_id_alter_projectcounter_id_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ProjectAlert",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"timespan_minutes",
|
||||
models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
("quantity", models.PositiveSmallIntegerField(blank=True, null=True)),
|
||||
("created", models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
(
|
||||
"project",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="projects.project",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(blank=True, max_length=255)),
|
||||
(
|
||||
"uptime",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Send alert on any uptime monitor check failure",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="AlertRecipient",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"recipient_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("email", "Email"),
|
||||
("webhook", "General Slack-compatible webhook"),
|
||||
("discord", "Discord"),
|
||||
("googlechat", "Google Chat webhook"),
|
||||
],
|
||||
max_length=16,
|
||||
),
|
||||
),
|
||||
("url", models.URLField(blank=True, max_length=2000)),
|
||||
(
|
||||
"alert",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="alerts.projectalert",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"unique_together": {("alert", "recipient_type", "url")},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Notification",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created", models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
("is_sent", models.BooleanField(default=False)),
|
||||
("issues", models.ManyToManyField(to="issue_events.issue")),
|
||||
(
|
||||
"project_alert",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="alerts.projectalert",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -11,7 +11,6 @@ from apps.difs.stacktrace_processor import StacktraceProcessor
|
||||
from apps.event_ingest.schema import ErrorIssueEventSchema, StackTraceFrame
|
||||
from apps.files.models import File, FileBlob
|
||||
from apps.projects.models import Project
|
||||
from events.models import Event
|
||||
|
||||
|
||||
def getLogger():
|
||||
@@ -48,45 +47,6 @@ def difs_assemble(project_slug, name, checksum, chunks, debug_id):
|
||||
getLogger().error("difs_assemble: %s", err)
|
||||
|
||||
|
||||
def difs_run_resolve_stacktrace(event_id):
|
||||
difs_resolve_stacktrace.delay(event_id)
|
||||
|
||||
|
||||
@shared_task
|
||||
def difs_resolve_stacktrace(event_id):
|
||||
event = Event.objects.get(event_id=event_id)
|
||||
event_json = event.data
|
||||
exception = event_json.get("exception")
|
||||
|
||||
if exception is None:
|
||||
# It is not a crash report event
|
||||
return
|
||||
|
||||
project_id = event.issue.project_id
|
||||
|
||||
difs = DebugInformationFile.objects.filter(project_id=project_id).order_by(
|
||||
"-created"
|
||||
)
|
||||
resolved_stracktrackes = []
|
||||
|
||||
for dif in difs:
|
||||
if StacktraceProcessor.is_supported(event_json, dif) is False:
|
||||
continue
|
||||
blobs = [dif.file.blob]
|
||||
with difs_concat_file_blobs_to_disk(blobs) as symbol_file:
|
||||
remapped_stacktrace = StacktraceProcessor.resolve_stacktrace(
|
||||
event_json, symbol_file.name
|
||||
)
|
||||
if remapped_stacktrace is not None and remapped_stacktrace.score > 0:
|
||||
resolved_stracktrackes.append(remapped_stacktrace)
|
||||
if len(resolved_stracktrackes) > 0:
|
||||
best_remapped_stacktrace = max(
|
||||
resolved_stracktrackes, key=lambda item: item.score
|
||||
)
|
||||
StacktraceProcessor.update_frames(event, best_remapped_stacktrace.frames)
|
||||
event.save()
|
||||
|
||||
|
||||
def event_difs_resolve_stacktrace(event: ErrorIssueEventSchema, project_id: int):
|
||||
difs = DebugInformationFile.objects.filter(project_id=project_id).order_by(
|
||||
"-created"
|
||||
|
||||
@@ -12,7 +12,6 @@ class Migration(migrations.Migration):
|
||||
"projects",
|
||||
"0001_squashed_0009_alter_project_id_alter_projectcounter_id_and_more",
|
||||
),
|
||||
("events", "0001_squashed_0003_auto_20210116_2110"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
# Generated by Django 5.0.3 on 2024-03-15 14:08
|
||||
|
||||
import datetime
|
||||
import django.contrib.postgres.fields.hstore
|
||||
import django.contrib.postgres.search
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
# Functions from the following migrations need manual copying.
|
||||
# Move them and any dependencies into this file, then update the
|
||||
# RunPython operations to refer to the local versions:
|
||||
# apps.performance.migrations.0004_remove_transactionevent_project_and_more
|
||||
# apps.performance.migrations.0008_transactionevent_duration
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
replaces = [
|
||||
("performance", "0001_initial"),
|
||||
("performance", "0002_auto_20210124_1745"),
|
||||
("performance", "0003_transactionevent"),
|
||||
("performance", "0004_remove_transactionevent_project_and_more"),
|
||||
("performance", "0005_alter_span_data"),
|
||||
("performance", "0006_rename_title_transactiongroup_transaction_and_more"),
|
||||
(
|
||||
"performance",
|
||||
"0007_transactionevent_tags_transactiongroup_search_vector_and_more",
|
||||
),
|
||||
("performance", "0008_transactionevent_duration"),
|
||||
("performance", "0009_alter_span_description"),
|
||||
("performance", "0010_alter_span_id_alter_transactiongroup_id"),
|
||||
("performance", "0011_alter_transactionevent_duration"),
|
||||
("performance", "0012_alter_span_op"),
|
||||
]
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"projects",
|
||||
"0001_squashed_0009_alter_project_id_alter_projectcounter_id_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="TransactionEvent",
|
||||
fields=[
|
||||
(
|
||||
"event_id",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("created", models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
(
|
||||
"data",
|
||||
models.JSONField(help_text="General event data that is searchable"),
|
||||
),
|
||||
(
|
||||
"timestamp",
|
||||
models.DateTimeField(
|
||||
blank=True,
|
||||
help_text="Date created as claimed by client it came from",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
("transaction", models.CharField(max_length=1024)),
|
||||
("start_timestamp", models.DateTimeField()),
|
||||
(
|
||||
"project",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="projects.project",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ["-created"],
|
||||
},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="transactionevent",
|
||||
name="project",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="transactionevent",
|
||||
name="trace_id",
|
||||
field=models.UUIDField(
|
||||
db_index=True, default="00000000000000000000000000000000"
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TransactionGroup",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created", models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
("transaction", models.CharField(max_length=1024)),
|
||||
("op", models.CharField(max_length=255)),
|
||||
("method", models.CharField(blank=True, max_length=255, null=True)),
|
||||
(
|
||||
"project",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="projects.project",
|
||||
),
|
||||
),
|
||||
(
|
||||
"search_vector",
|
||||
django.contrib.postgres.search.SearchVectorField(
|
||||
editable=False, null=True
|
||||
),
|
||||
),
|
||||
("tags", models.JSONField(default=dict)),
|
||||
],
|
||||
options={
|
||||
"unique_together": {("transaction", "project", "op", "method")},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="transactionevent",
|
||||
name="group",
|
||||
field=models.ForeignKey(
|
||||
default=1,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="performance.transactiongroup",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Span",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created", models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
("span_id", models.CharField(max_length=16)),
|
||||
(
|
||||
"parent_span_id",
|
||||
models.CharField(blank=True, max_length=16, null=True),
|
||||
),
|
||||
("op", models.CharField(max_length=255)),
|
||||
(
|
||||
"description",
|
||||
models.CharField(blank=True, max_length=1024, null=True),
|
||||
),
|
||||
("start_timestamp", models.DateTimeField()),
|
||||
("timestamp", models.DateTimeField()),
|
||||
(
|
||||
"tags",
|
||||
django.contrib.postgres.fields.hstore.HStoreField(default=dict),
|
||||
),
|
||||
("data", models.JSONField(default=dict)),
|
||||
(
|
||||
"transaction",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="performance.transactionevent",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="transactionevent",
|
||||
name="transaction",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="transactionevent",
|
||||
name="tags",
|
||||
field=django.contrib.postgres.fields.hstore.HStoreField(default=dict),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="transactionevent",
|
||||
name="duration",
|
||||
field=models.DurationField(db_index=True, default=datetime.timedelta(0)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="span",
|
||||
name="description",
|
||||
field=models.CharField(blank=True, max_length=2000, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="span",
|
||||
name="id",
|
||||
field=models.BigAutoField(
|
||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="transactiongroup",
|
||||
name="id",
|
||||
field=models.BigAutoField(
|
||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||
),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="transactionevent",
|
||||
name="duration",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="transactionevent",
|
||||
name="duration",
|
||||
field=models.PositiveIntegerField(
|
||||
db_index=True, default=0, help_text="Milliseconds"
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="span",
|
||||
name="op",
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -1,12 +1,37 @@
|
||||
import uuid
|
||||
|
||||
from django.contrib.postgres.fields import HStoreField
|
||||
from django.contrib.postgres.search import SearchVectorField
|
||||
from django.db import models
|
||||
|
||||
from apps.projects.tasks import update_transaction_event_project_hourly_statistic
|
||||
from events.models import AbstractEvent
|
||||
from glitchtip.base_models import CreatedModel
|
||||
|
||||
|
||||
class AbstractEvent(CreatedModel):
|
||||
event_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
data = models.JSONField(help_text="General event data that is searchable")
|
||||
timestamp = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Date created as claimed by client it came from",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __str__(self):
|
||||
return self.event_id_hex
|
||||
|
||||
@property
|
||||
def event_id_hex(self):
|
||||
"""The public key without dashes"""
|
||||
if self.event_id:
|
||||
if isinstance(self.event_id, str):
|
||||
return self.event_id
|
||||
return self.event_id.hex
|
||||
|
||||
|
||||
class TransactionGroup(CreatedModel):
|
||||
transaction = models.CharField(max_length=1024)
|
||||
project = models.ForeignKey("projects.Project", on_delete=models.CASCADE)
|
||||
|
||||
@@ -30,6 +30,8 @@ def daterange(start_date, end_date):
|
||||
|
||||
|
||||
def migrate_issue_events(apps, schema_editor):
|
||||
if settings.TESTING:
|
||||
return
|
||||
today = now()
|
||||
days_ago = timedelta(days=settings.GLITCHTIP_MAX_EVENT_LIFE_DAYS + 1)
|
||||
with connection.cursor() as cursor:
|
||||
|
||||
@@ -1,37 +1,6 @@
|
||||
import uuid
|
||||
|
||||
from django.contrib.postgres.fields import HStoreField
|
||||
from django.db import models
|
||||
|
||||
from apps.projects.tasks import update_event_project_hourly_statistic
|
||||
from glitchtip.base_models import CreatedModel
|
||||
from glitchtip.model_utils import FromStringIntegerChoices
|
||||
|
||||
|
||||
class AbstractEvent(CreatedModel):
|
||||
event_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
data = models.JSONField(help_text="General event data that is searchable")
|
||||
timestamp = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Date created as claimed by client it came from",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __str__(self):
|
||||
return self.event_id_hex
|
||||
|
||||
@property
|
||||
def event_id_hex(self):
|
||||
"""The public key without dashes"""
|
||||
if self.event_id:
|
||||
if isinstance(self.event_id, str):
|
||||
return self.event_id
|
||||
return self.event_id.hex
|
||||
|
||||
|
||||
class LogLevel(FromStringIntegerChoices):
|
||||
NOTSET = 0, "sample"
|
||||
DEBUG = 1, "debug"
|
||||
@@ -50,131 +19,3 @@ class LogLevel(FromStringIntegerChoices):
|
||||
if string == "log":
|
||||
return cls.INFO
|
||||
return cls.ERROR
|
||||
|
||||
|
||||
class Event(AbstractEvent):
|
||||
"""
|
||||
An individual event. An issue is a set of like-events.
|
||||
Most is stored in "data" but some fields benefit from being real
|
||||
relational data types such as dates and foreign keys
|
||||
"""
|
||||
|
||||
issue = models.ForeignKey(
|
||||
"issues.Issue",
|
||||
on_delete=models.CASCADE,
|
||||
help_text="Sentry calls this a group",
|
||||
)
|
||||
level = models.PositiveSmallIntegerField(
|
||||
choices=LogLevel.choices, default=LogLevel.ERROR
|
||||
)
|
||||
errors = models.JSONField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Event processing errors from event intake, including validation errors",
|
||||
)
|
||||
tags = HStoreField(default=dict)
|
||||
release = models.ForeignKey(
|
||||
"releases.Release", blank=True, null=True, on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created"]
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
is_new = self._state.adding
|
||||
super().save(*args, **kwargs)
|
||||
if is_new:
|
||||
update_event_project_hourly_statistic(
|
||||
args=[self.issue.project_id, self.created], countdown=60
|
||||
)
|
||||
|
||||
def event_json(self):
|
||||
"""
|
||||
OSS Sentry Compatible raw event JSON
|
||||
Effectively this combines data and relational data
|
||||
"""
|
||||
event = self.data
|
||||
event["event_id"] = self.event_id_hex
|
||||
event["project"] = self.issue.project_id
|
||||
event["level"] = self.get_level_display()
|
||||
event["tags"] = self.tags.items()
|
||||
if self.errors:
|
||||
event["errors"] = self.errors
|
||||
if self.timestamp:
|
||||
event["datetime"] = self.timestamp.isoformat().replace("+00:00", "Z")
|
||||
if self.release:
|
||||
event["release"] = self.release.version
|
||||
return event
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
return self.data.get("extra")
|
||||
|
||||
@property
|
||||
def contexts(self):
|
||||
return self.data.get("contexts")
|
||||
|
||||
@property
|
||||
def culprit(self):
|
||||
return self.data.get("culprit")
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
return self.data.get("message")
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
return self.data.get("user")
|
||||
|
||||
def _build_context(self, context: list, base_line_no: int, is_pre: bool):
|
||||
context_length = len(context)
|
||||
result = []
|
||||
for index, pre_context_line in enumerate(context):
|
||||
if is_pre:
|
||||
line_no = base_line_no - context_length + index
|
||||
else:
|
||||
line_no = base_line_no + 1 + index
|
||||
result.append(
|
||||
[
|
||||
line_no,
|
||||
pre_context_line,
|
||||
]
|
||||
)
|
||||
return result
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
return self.data.get("metadata")
|
||||
|
||||
@property
|
||||
def packages(self):
|
||||
return self.data.get("modules")
|
||||
|
||||
@property
|
||||
def platform(self):
|
||||
return self.data.get("platform")
|
||||
|
||||
@property
|
||||
def sdk(self):
|
||||
return self.data.get("sdk")
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self.data.get("title")
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self.data.get("type")
|
||||
|
||||
def next(self, *args, **kwargs):
|
||||
try:
|
||||
return self.get_next_by_created(**kwargs)
|
||||
except Event.DoesNotExist:
|
||||
return None
|
||||
|
||||
def previous(self, *args, **kwargs):
|
||||
"""Get previous object by created date, pass filter kwargs"""
|
||||
try:
|
||||
return self.get_previous_by_created(**kwargs)
|
||||
except Event.DoesNotExist:
|
||||
return None
|
||||
+1
-111
@@ -1,14 +1,7 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.postgres.indexes import GinIndex
|
||||
from django.contrib.postgres.search import SearchVectorField
|
||||
from django.db import connection, models
|
||||
from django.db import models
|
||||
|
||||
from events.models import LogLevel
|
||||
from glitchtip.base_models import CreatedModel
|
||||
from glitchtip.model_utils import FromStringIntegerChoices
|
||||
|
||||
from .utils import base32_encode
|
||||
|
||||
|
||||
class EventType(models.IntegerChoices):
|
||||
DEFAULT = 0, "default"
|
||||
@@ -21,106 +14,3 @@ class EventStatus(FromStringIntegerChoices):
|
||||
UNRESOLVED = 0, "unresolved"
|
||||
RESOLVED = 1, "resolved"
|
||||
IGNORED = 2, "ignored"
|
||||
|
||||
|
||||
class Issue(CreatedModel):
|
||||
"""
|
||||
Sentry called this a "group". A issue is a collection of events with meta data
|
||||
such as resolved status.
|
||||
"""
|
||||
|
||||
# annotations Not implemented
|
||||
# assigned_to Not implemented
|
||||
culprit = models.CharField(max_length=1024, blank=True, null=True)
|
||||
has_seen = models.BooleanField(default=False)
|
||||
# is_bookmarked Not implement - is per user
|
||||
is_public = models.BooleanField(default=False)
|
||||
level = models.PositiveSmallIntegerField(
|
||||
choices=LogLevel.choices, default=LogLevel.ERROR
|
||||
)
|
||||
metadata = models.JSONField()
|
||||
tags = models.JSONField(default=dict)
|
||||
project = models.ForeignKey("projects.Project", on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=255)
|
||||
type = models.PositiveSmallIntegerField(
|
||||
choices=EventType.choices, default=EventType.DEFAULT
|
||||
)
|
||||
status = models.PositiveSmallIntegerField(
|
||||
choices=EventStatus.choices, default=EventStatus.UNRESOLVED
|
||||
)
|
||||
# See migration 0004 for trigger that sets search_vector, count, last_seen
|
||||
short_id = models.PositiveIntegerField(null=True)
|
||||
search_vector = SearchVectorField(null=True, editable=False)
|
||||
count = models.PositiveIntegerField(default=1, editable=False)
|
||||
last_seen = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("project", "short_id"),)
|
||||
indexes = [GinIndex(fields=["search_vector"], name="search_vector_idx")]
|
||||
|
||||
def event(self):
|
||||
return self.event_set.first()
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def check_for_status_update(self):
|
||||
"""
|
||||
Determine if issue should regress back to unresolved
|
||||
Typically run when processing a new event related to the issue
|
||||
"""
|
||||
if self.status == EventStatus.RESOLVED:
|
||||
self.status = EventStatus.UNRESOLVED
|
||||
self.save()
|
||||
# Delete notifications so that new alerts are sent for regressions
|
||||
self.notification_set.all().delete()
|
||||
|
||||
def get_hex_color(self):
|
||||
if self.level == LogLevel.INFO:
|
||||
return "#4b60b4"
|
||||
elif self.level is LogLevel.WARNING:
|
||||
return "#e9b949"
|
||||
elif self.level in [LogLevel.ERROR, LogLevel.FATAL]:
|
||||
return "#e52b50"
|
||||
|
||||
@property
|
||||
def short_id_display(self):
|
||||
"""
|
||||
Short IDs are per project issue counters. They show as PROJECT_SLUG-ID_BASE32
|
||||
The intention is to be human readable identifiers that can reference an issue.
|
||||
"""
|
||||
if self.short_id is not None:
|
||||
return f"{self.project.slug.upper()}-{base32_encode(self.short_id)}"
|
||||
return ""
|
||||
|
||||
def get_detail_url(self):
|
||||
return f"{settings.GLITCHTIP_URL.geturl()}/{self.project.organization.slug}/issues/{self.pk}"
|
||||
|
||||
@classmethod
|
||||
def update_index(cls, issue_id: int):
|
||||
"""
|
||||
Update search index/tag aggregations
|
||||
"""
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("CALL update_issue_index(%s)", [issue_id])
|
||||
|
||||
|
||||
class IssueHash(models.Model):
|
||||
issue = models.ForeignKey("issues.Issue", on_delete=models.CASCADE)
|
||||
# Redundant project allows for unique constraint
|
||||
project = models.ForeignKey("projects.Project", on_delete=models.CASCADE)
|
||||
value = models.UUIDField(db_index=True)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=["project", "value"], name="project hash")
|
||||
]
|
||||
|
||||
|
||||
class Comment(CreatedModel):
|
||||
issue = models.ForeignKey("issues.Issue", on_delete=models.CASCADE)
|
||||
user = models.ForeignKey("users.User", null=True, on_delete=models.SET_NULL)
|
||||
text = models.TextField(blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ("-created",)
|
||||
Reference in new issue
Block a user