Date/issue_count correct in list_view

This commit is contained in:
Klaas van Schelven
2024-02-20 17:49:48 +01:00
parent 9550c5f1dc
commit 20361ce75a
9 changed files with 103 additions and 17 deletions

View File

@@ -5,6 +5,7 @@ from django.test import TestCase as DjangoTestCase
from projects.models import Project
from issues.models import Issue
from issues.factories import denormalized_issue_fields
from events.models import Event
from events.factories import create_event
@@ -180,6 +181,7 @@ class PCRegistryTestCase(DjangoTestCase):
project=project,
is_muted=True,
unmute_on_volume_based_conditions='[{"period": "day", "nr_of_periods": 1, "volume": 100}]',
**denormalized_issue_fields(),
)
create_event(project, issue)

View File

@@ -106,6 +106,11 @@ class BaseIngestAPIView(APIView):
issue, issue_created = Issue.objects.get_or_create(
project=ingested_event.project,
hash=hash_,
defaults={
"first_seen": ingested_event.timestamp,
"last_seen": ingested_event.timestamp,
"event_count": 1,
},
)
event, event_created = Event.from_ingested(ingested_event, issue, event_data)
@@ -121,11 +126,17 @@ class BaseIngestAPIView(APIView):
if ingested_event.project.alert_on_new_issue:
send_new_issue_alert.delay(issue.id)
elif issue_is_regression(issue, event.release): # new issues cannot be regressions by definition, hence 'else'
if ingested_event.project.alert_on_regression:
send_regression_alert.delay(issue.id)
else:
# new issues cannot be regressions by definition, hence this is in the 'else' branch
if issue_is_regression(issue, event.release):
if ingested_event.project.alert_on_regression:
send_regression_alert.delay(issue.id)
IssueStateManager.reopen(issue)
IssueStateManager.reopen(issue)
# update the denormalized fields
issue.last_seen = ingested_event.timestamp
issue.event_count += 1
if issue.id not in get_pc_registry().by_issue:
pc_registry.by_issue[issue.id] = PeriodCounter()

View File

@@ -19,6 +19,7 @@ def get_or_create_issue(project=None, event_data=None):
issue, issue_created = Issue.objects.get_or_create(
project=project,
hash=hash_,
defaults=denormalized_issue_fields(),
)
return issue, issue_created
@@ -31,3 +32,12 @@ def create_event_data():
"timestamp": timezone.now().isoformat(),
"platform": "python",
}
def denormalized_issue_fields():
"""return a dict of fields that are expected to be denormalized on Issue; for testing purposes"""
return {
"first_seen": timezone.now(),
"last_seen": timezone.now(),
"event_count": 1,
}

View File

@@ -0,0 +1,30 @@
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('issues', '0007_remove_issue_events'),
]
operations = [
migrations.AddField(
model_name='issue',
name='event_count',
field=models.IntegerField(default=1),
preserve_default=False,
),
migrations.AddField(
model_name='issue',
name='first_seen',
field=models.DateTimeField(default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='issue',
name='last_seen',
field=models.DateTimeField(default=django.utils.timezone.now),
preserve_default=False,
),
]

View File

@@ -0,0 +1,19 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('issues', '0008_issue_event_count_issue_first_seen_issue_last_seen'),
]
operations = [
migrations.AddIndex(
model_name='issue',
index=models.Index(fields=['first_seen'], name='issues_issu_first_s_9fb0f9_idx'),
),
migrations.AddIndex(
model_name='issue',
index=models.Index(fields=['last_seen'], name='issues_issu_last_se_400a05_idx'),
),
]

View File

@@ -15,6 +15,11 @@ class Issue(models.Model):
"projects.Project", blank=False, null=True, on_delete=models.SET_NULL) # SET_NULL: cleanup 'later'
hash = models.CharField(max_length=32, blank=False, null=False)
# denormalized fields:
last_seen = models.DateTimeField(blank=False, null=False)
first_seen = models.DateTimeField(blank=False, null=False)
event_count = models.IntegerField(blank=False, null=False)
# fields related to resolution:
# what does this mean for the release-based use cases? it means what you filter on.
# it also simply means: it was "marked as resolved" after the last regression (if any)
@@ -23,6 +28,7 @@ class Issue(models.Model):
fixed_at = models.TextField(blank=False, null=False, default='[]')
events_at = models.TextField(blank=False, null=False, default='[]')
# fields related to muting:
is_muted = models.BooleanField(default=False)
unmute_on_volume_based_conditions = models.TextField(blank=False, null=False, default="[]") # json string
@@ -74,6 +80,12 @@ class Issue(models.Model):
def occurs_in_last_release(self):
return False # TODO actually implement (and then: implement in a performant manner)
class Meta:
indexes = [
models.Index(fields=["first_seen"]),
models.Index(fields=["last_seen"]),
]
class IssueStateManager(object):
"""basically: a namespace; with static methods that combine field-setting in a single place"""

View File

@@ -50,11 +50,11 @@ https://flowbite.com/docs/forms/floating-label/
</td>
<td class="text-right font-bold p-4">
FOO
{# FOO #}
</td>
<td class="text-right font-bold p-4">
BAR
{# BAR #}
</td>
</tr>
@@ -69,24 +69,22 @@ https://flowbite.com/docs/forms/floating-label/
<div>
<a href="/issues/issue/{{ issue.id }}/event/last/" class="text-cyan-500 font-bold">{{ issue.title|truncatechars:100 }}</a>
</div>
<div class="text-sm">from <b>8 Nov 12:33</b> | last <b>8 Nov 12:39</b> | with <b>3</b> events
<div class="text-sm">from <b>{{ issue.first_seen|date:"d M G:i" }}</b> | last <b>{{ issue.last_seen|date:"d M G:i" }}</b> | with <b>{{ issue.event_count }}</b> events
{% if issue.parsed_data.transaction %}| <span class="font-bold">{{ issue.parsed_data.transaction }} </span>{% endif %}
</div>
</td>
{% comment %}
[ ] first release (or commit), last release (or commit)
[ ] first seen
[ ] last seen.
[ ] assigned to
[ ] n.r. of events?
{% endcomment %}
<td class="text-right p-4">
ABC
{# ABC #}
</td>
<td class="text-right p-4">
123
{# 123 #}
</td>
</tr>

View File

@@ -10,6 +10,7 @@ from bugsink.period_counter import PeriodCounter, TL_DAY
from .models import Issue, IssueStateManager
from .regressions import is_regression, is_regression_2, issue_is_regression
from .factories import denormalized_issue_fields
def fresh(obj):
@@ -164,7 +165,7 @@ class RegressionIssueTestCase(DjangoTestCase):
project = Project.objects.create()
# new issue is not a regression
issue = Issue.objects.create(project=project)
issue = Issue.objects.create(project=project, **denormalized_issue_fields())
self.assertFalse(issue_is_regression(fresh(issue), "anything"))
# resolve the issue, a reoccurrence is a regression
@@ -185,7 +186,7 @@ class RegressionIssueTestCase(DjangoTestCase):
create_release_if_needed(project, "2.0.0")
# new issue is not a regression
issue = Issue.objects.create(project=project)
issue = Issue.objects.create(project=project, **denormalized_issue_fields())
self.assertFalse(issue_is_regression(fresh(issue), "anything"))
# resolve the by latest, reoccurrences of older releases are not regressions but occurrences by latest are
@@ -212,7 +213,7 @@ class RegressionIssueTestCase(DjangoTestCase):
create_release_if_needed(project, "2.0.0")
# new issue is not a regression
issue = Issue.objects.create(project=project)
issue = Issue.objects.create(project=project, **denormalized_issue_fields())
self.assertFalse(issue_is_regression(fresh(issue), "anything"))
# resolve the by next, reoccurrences of any existing releases are not regressions
@@ -296,7 +297,7 @@ class MuteUnmuteTestCase(TestCase):
def test_mute_no_vbc_for_unmute(self):
project = Project.objects.create()
issue = Issue.objects.create(project=project)
issue = Issue.objects.create(project=project, **denormalized_issue_fields())
IssueStateManager.mute(issue, "[]")
issue.save()
@@ -306,7 +307,7 @@ class MuteUnmuteTestCase(TestCase):
def test_mute_simple_case(self):
project = Project.objects.create()
issue = Issue.objects.create(project=project)
issue = Issue.objects.create(project=project, **denormalized_issue_fields())
IssueStateManager.mute(issue, "[{\"period\": \"day\", \"nr_of_periods\": 1, \"volume\": 1}]")
issue.save()
@@ -321,6 +322,7 @@ class MuteUnmuteTestCase(TestCase):
project=project,
unmute_on_volume_based_conditions='[{"period": "day", "nr_of_periods": 1, "volume": 1}]',
is_muted=True,
**denormalized_issue_fields(),
)
pc = registry.by_issue[issue.id] = PeriodCounter()
@@ -337,6 +339,7 @@ class MuteUnmuteTestCase(TestCase):
project=project,
unmute_on_volume_based_conditions='[{"period": "day", "nr_of_periods": 1, "volume": 1}]',
is_muted=True,
**denormalized_issue_fields(),
)
# because we create our objects before getting the lazy registry, event-listeners will be correctly set by
@@ -362,6 +365,7 @@ class MuteUnmuteTestCase(TestCase):
{"period": "month", "nr_of_periods": 1, "volume": 1}
]''',
is_muted=True,
**denormalized_issue_fields(),
)
# because we create our objects before getting the lazy registry, event-listeners will be correctly set by

View File

@@ -10,7 +10,7 @@ from .models import Issue, IssueStateManager
def issue_list(request, project_id):
issue_list = Issue.objects.filter(project_id=project_id)
issue_list = Issue.objects.filter(project_id=project_id).order_by("-last_seen")
project = get_object_or_404(Project, pk=project_id)
return render(request, "issues/issue_list.html", {