Skip to content

Commit 0909199

Browse files
committed
Highlight untriaged reports
1 parent fb030cd commit 0909199

8 files changed

Lines changed: 196 additions & 7 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ uv run --extra=dev tox -e e2e
7777

7878
Note: E2E tests require the frontend to be built first. Run `npm run build` or `npm run start` in `server/frontend/` before running E2E tests.
7979

80+
E2E test data lives in `tests/e2e/fixtures/fixtures.json` and is generated by `tests/e2e/scripts/generate_e2e_fixtures.py`. Edit `BUCKETS_CONFIG` in that script to add or modify test buckets and their reports, then regenerate the fixture file:
81+
82+
```
83+
uv run --extra=server python tests/e2e/scripts/generate_e2e_fixtures.py
84+
```
8085

8186
To run all python tests excluding E2E tests:
8287

server/frontend/src/components/Buckets/ReportPreviewRow.vue

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<template>
2-
<tr @click="report.view_url">
2+
<tr @click="report.view_url" :class="{ warning: isNewAfterUntriage }">
33
<td class="wrap-normal">
4-
{{ formatShorterDate(report.reported_at) }}<br />
4+
<span v-if="isNewAfterUntriage" class="label label-warning">new</span
5+
><br />
6+
{{ formatShorterDate(report.reported_at) }}
57
(<a :href="report.view_url">Full details</a>)
68
</td>
79
<td class="url-col">
@@ -96,6 +98,16 @@ export default {
9698
type: Object,
9799
required: true,
98100
},
101+
triagedAt: {
102+
type: String,
103+
default: null,
104+
},
105+
},
106+
computed: {
107+
isNewAfterUntriage() {
108+
if (!this.triagedAt || !this.report.reported_at) return false;
109+
return new Date(this.report.reported_at) > new Date(this.triagedAt);
110+
},
99111
},
100112
methods: {
101113
formatShorterDate: shorterDate,

server/frontend/src/components/Buckets/View.vue

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@
3131
</div>
3232
</td>
3333
<td v-else>
34+
<div
35+
v-if="lastTriagedAt"
36+
class="alert alert-warning last-triaged"
37+
data-testid="untriage-warning"
38+
>
39+
<i class="bi bi-exclamation-triangle-fill"></i>
40+
This bucket was last triaged on
41+
{{ formatDate(lastTriagedAt) }} but was automatically untriaged
42+
due to a spike in activity. Reports causing the spike are
43+
highlighted below.
44+
</div>
3445
No bug associated.
3546
<span v-if="triageStatus" data-testid="triage-status-display"
3647
>Marked triaged as:
@@ -208,6 +219,7 @@
208219
v-else
209220
:key="report.id"
210221
:report="report"
222+
:triaged-at="lastTriagedAt"
211223
/>
212224
</tbody>
213225
</table>
@@ -313,6 +325,9 @@ export default {
313325
};
314326
},
315327
computed: {
328+
lastTriagedAt() {
329+
return !this.triageStatus && this.triagedAt ? this.triagedAt : null;
330+
},
316331
prettySignature() {
317332
return jsonPretty(this.bucket.signature);
318333
},
@@ -586,4 +601,8 @@ button[aria-expanded="false"] .bi-eye-slash-fill {
586601
padding: 0;
587602
margin: 0.25em 0 0;
588603
}
604+
605+
.last-triaged {
606+
max-width: 500px;
607+
}
589608
</style>

tests/e2e/fixtures/fixtures.json

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,22 @@
231231
"cluster": 99999
232232
}
233233
},
234+
{
235+
"model": "reportmanager.bucket",
236+
"pk": 888888,
237+
"fields": {
238+
"bug": null,
239+
"color": null,
240+
"description": "www.example-spike.com [Cluster 88888]",
241+
"domain": "www.example-spike.com",
242+
"triage_status": null,
243+
"triaged_at": "2026-04-10T18:05:00+00:00",
244+
"priority": 0,
245+
"signature": "{\"symptoms\": [{\"type\": \"url\", \"part\": \"hostname\", \"value\": \"www.example-spike.com\"}]}",
246+
"reassign_in_progress": false,
247+
"cluster": 88888
248+
}
249+
},
234250
{
235251
"model": "reportmanager.reportentry",
236252
"pk": 1000,
@@ -672,6 +688,69 @@
672688
"cluster": 99999
673689
}
674690
},
691+
{
692+
"model": "reportmanager.reportentry",
693+
"pk": 1021,
694+
"fields": {
695+
"app": 1,
696+
"breakage_category": 1,
697+
"bucket": 888888,
698+
"comments": "Old report before triage",
699+
"comments_translated": "Old report before triage",
700+
"comments_original_language": "en",
701+
"comments_preprocessed": "old report before triage",
702+
"details": {},
703+
"domain": "www.example-spike.com",
704+
"ml_valid_probability": 0.9,
705+
"os": 1,
706+
"reported_at": "2026-04-08T18:05:00+00:00",
707+
"url": "https://www.example-spike.com/",
708+
"uuid": "00000000-0000-0000-0000-000000001021",
709+
"cluster": 88888
710+
}
711+
},
712+
{
713+
"model": "reportmanager.reportentry",
714+
"pk": 1022,
715+
"fields": {
716+
"app": 2,
717+
"breakage_category": 1,
718+
"bucket": 888888,
719+
"comments": "New report after spike untriage",
720+
"comments_translated": "New report after spike untriage",
721+
"comments_original_language": "en",
722+
"comments_preprocessed": "new report after spike untriage",
723+
"details": {},
724+
"domain": "www.example-spike.com",
725+
"ml_valid_probability": 0.88,
726+
"os": 2,
727+
"reported_at": "2026-04-12T18:05:00+00:00",
728+
"url": "https://www.example-spike.com/",
729+
"uuid": "00000000-0000-0000-0000-000000001022",
730+
"cluster": 88888
731+
}
732+
},
733+
{
734+
"model": "reportmanager.reportentry",
735+
"pk": 1023,
736+
"fields": {
737+
"app": 3,
738+
"breakage_category": 2,
739+
"bucket": 888888,
740+
"comments": "Another new report after spike untriage",
741+
"comments_translated": "Another new report after spike untriage",
742+
"comments_original_language": "en",
743+
"comments_preprocessed": "another new report after spike untriage",
744+
"details": {},
745+
"domain": "www.example-spike.com",
746+
"ml_valid_probability": 0.85,
747+
"os": 3,
748+
"reported_at": "2026-04-14T18:05:00+00:00",
749+
"url": "https://www.example-spike.com/",
750+
"uuid": "00000000-0000-0000-0000-000000001023",
751+
"cluster": 88888
752+
}
753+
},
675754
{
676755
"model": "reportmanager.cluster",
677756
"pk": 70854,
@@ -711,5 +790,13 @@
711790
"domain": "www.example-triaged.com",
712791
"centroid": 1019
713792
}
793+
},
794+
{
795+
"model": "reportmanager.cluster",
796+
"pk": 88888,
797+
"fields": {
798+
"domain": "www.example-spike.com",
799+
"centroid": 1021
800+
}
714801
}
715802
]

tests/e2e/fixtures/reports_data.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.contrib.contenttypes.models import ContentType
66
from django.core.management import call_command
77

8-
from reportmanager.models import Bucket
8+
from reportmanager.models import Bucket, ReportEntry
99
from reportmanager.models import User as ReportManagerUser
1010

1111
from .defaults import TEST_USER_EMAIL, TEST_USER_PASSWORD
@@ -28,6 +28,9 @@ def e2e_data(db):
2828
)
2929
user.user_permissions.add(write_perm)
3030

31+
spike_bucket = Bucket.objects.get(pk=888888)
32+
spike_triaged_at = spike_bucket.triaged_at
33+
3134
return {
3235
"bucket_count": Bucket.objects.count(),
3336
"triaged_bucket_id": Bucket.objects.filter(triage_status__isnull=False)
@@ -39,4 +42,8 @@ def e2e_data(db):
3942
.first()
4043
.id,
4144
"triaged_count": Bucket.objects.filter(triage_status__isnull=False).count(),
45+
"spike_bucket_id": spike_bucket.id,
46+
"spike_new_report_count": ReportEntry.objects.filter(
47+
bucket=spike_bucket, reported_at__gt=spike_triaged_at
48+
).count(),
4249
}

tests/e2e/page_objects/bucket_view.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def __init__(self, page, live_server, bucket_id):
1616
self.triage_status_display = self.page.get_by_test_id("triage-status-display")
1717
self.triage_status_label = self.page.get_by_test_id("triage-status-label")
1818
self.unmark_option = self.page.get_by_test_id("triage-unmark")
19+
self.untriage_warning = self.page.get_by_test_id("untriage-warning")
1920

2021
def wait_for_loaded(self):
2122
"""Wait for the bucket view's triage trigger to be visible."""
@@ -39,3 +40,6 @@ def expect_triage_label(self, text):
3940

4041
def expect_triage_trigger(self, text):
4142
expect(self.triage_trigger).to_have_text(text)
43+
44+
def highlighted_report_rows(self):
45+
return self.page.locator("tr.warning")

tests/e2e/scripts/generate_e2e_fixtures.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,45 @@
258258
},
259259
],
260260
},
261+
{
262+
"id": 888888,
263+
"domain": "www.example-spike.com",
264+
"description": "www.example-spike.com [Cluster 88888]",
265+
"cluster_id": 88888,
266+
"priority": 0,
267+
"triage_status": None,
268+
"triaged_at": ANCHOR_DATE - timedelta(days=5),
269+
"signature": '{"symptoms": [{"type": "url", "part": "hostname", "value": "www.example-spike.com"}]}',
270+
"comments": [
271+
{
272+
# reported 7 days before ANCHOR_DATE = 2 days before triaged_at -> not highlighted
273+
"reported_at": ANCHOR_DATE - timedelta(days=7),
274+
"comments": "Old report before triage",
275+
"comments_translated": "Old report before triage",
276+
"comments_original_language": "en",
277+
"breakage_category": "load",
278+
"ml_valid_probability": 0.9,
279+
},
280+
{
281+
# reported 3 days before ANCHOR_DATE = 2 days after triaged_at -> highlighted
282+
"reported_at": ANCHOR_DATE - timedelta(days=3),
283+
"comments": "New report after spike untriage",
284+
"comments_translated": "New report after spike untriage",
285+
"comments_original_language": "en",
286+
"breakage_category": "load",
287+
"ml_valid_probability": 0.88,
288+
},
289+
{
290+
# reported 1 day before ANCHOR_DATE = 4 days after triaged_at -> highlighted
291+
"reported_at": ANCHOR_DATE - timedelta(days=1),
292+
"comments": "Another new report after spike untriage",
293+
"comments_translated": "Another new report after spike untriage",
294+
"comments_original_language": "en",
295+
"breakage_category": "media",
296+
"ml_valid_probability": 0.85,
297+
},
298+
],
299+
},
261300
]
262301

263302
# Supporting data
@@ -375,7 +414,11 @@ def generate_fixtures():
375414
comments_translated.lower() if comments_translated else ""
376415
)
377416

378-
days_ago = date_offsets[i % len(date_offsets)]
417+
if "reported_at" in comment_data:
418+
reported_at = comment_data["reported_at"]
419+
else:
420+
days_ago = date_offsets[i % len(date_offsets)]
421+
reported_at = ANCHOR_DATE - timedelta(days=days_ago)
379422

380423
fixture.append(
381424
{
@@ -396,9 +439,7 @@ def generate_fixtures():
396439
"domain": bucket_config["domain"],
397440
"ml_valid_probability": comment_data["ml_valid_probability"],
398441
"os": (i % len(OS_DATA)) + 1, # Rotate through all OS
399-
"reported_at": (
400-
ANCHOR_DATE - timedelta(days=days_ago)
401-
).isoformat(),
442+
"reported_at": reported_at.isoformat(),
402443
"url": f"https://{bucket_config['domain']}/",
403444
"uuid": f"00000000-0000-0000-0000-{report_id:012d}",
404445
"cluster": bucket_config["cluster_id"],

tests/e2e/test_bucket_view.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
from playwright.sync_api import expect
23

34
from .page_objects.bucket_view import BucketViewPage
45

@@ -50,3 +51,16 @@ def test_change_and_unmark_triage_status(bucket_view_page, e2e_data):
5051
bucket_view.unmark_triage()
5152
bucket_view.expect_triage_trigger("Mark triaged")
5253
assert bucket_view.triage_status_display.count() == 0
54+
55+
56+
@pytest.mark.ui
57+
@pytest.mark.django_db(transaction=True)
58+
def test_spike_untriage_warning_shown(bucket_view_page, e2e_data):
59+
bucket_view = bucket_view_page(e2e_data["spike_bucket_id"])
60+
61+
# Warning banner should be visible since triage_status is None but triaged_at is set
62+
expect(bucket_view.untriage_warning).to_be_visible()
63+
expect(bucket_view.untriage_warning).to_contain_text("spike in activity")
64+
expect(bucket_view.highlighted_report_rows()).to_have_count(
65+
e2e_data["spike_new_report_count"]
66+
)

0 commit comments

Comments
 (0)