mirror of
https://github.com/jlengrand/bugsink.git
synced 2026-03-10 08:01:17 +00:00
Date/issue_count correct in list_view
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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"""
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", {
|
||||
|
||||
Reference in New Issue
Block a user