From 1e45025240f40c6baa91a1079d0d287b0bedf2ca Mon Sep 17 00:00:00 2001 From: Alex Ionescu Date: Mon, 10 Jul 2023 10:39:27 +0300 Subject: [PATCH] Bug 1838836 - Add TaskclusterMetadata to the alert summary API endpoint --- tests/conftest.py | 73 +++++++++++++++++++ .../webapp/api/test_performance_alerts_api.py | 24 +++++- .../api/test_performance_alertsummary_api.py | 44 ++++++++++- .../webapp/api/performance_serializers.py | 53 +++++++++++++- ui/perfherder/alerts/AlertActionPanel.jsx | 1 + 5 files changed, 191 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a58fc1ec95a..ede958a541d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -349,6 +349,11 @@ def test_job_2(eleven_job_blobs, create_jobs): return create_jobs(eleven_job_blobs[0:2])[1] +@pytest.fixture +def test_job_3(eleven_job_blobs, create_jobs): + return create_jobs(eleven_job_blobs[0:3])[2] + + @pytest.fixture def mock_log_parser(monkeypatch): from celery import shared_task @@ -666,6 +671,37 @@ def create_perf_signature( last_updated=datetime.datetime.now(), ) +@pytest.fixture +def test_taskcluster_metadata(test_job_2) -> th_models.TaskclusterMetadata: + return create_taskcluster_metadata(test_job_2) + + +@pytest.fixture +def test_taskcluster_metadata_2(test_job_3) -> th_models.TaskclusterMetadata: + return create_taskcluster_metadata_2(test_job_3) + + +def create_taskcluster_metadata( + test_job_2 +) -> th_models.TaskclusterMetadata: + + return th_models.TaskclusterMetadata.objects.create( + job=test_job_2, + task_id='V3SVuxO8TFy37En_6HcXLp', + retry_id='0', + ) + + +def create_taskcluster_metadata_2( + test_job_3 +) -> th_models.TaskclusterMetadata: + + return th_models.TaskclusterMetadata.objects.create( + job=test_job_3, + task_id='V3SVuxO8TFy37En_6HcXLq', + retry_id='0', + ) + @pytest.fixture def test_perf_signature_2(test_perf_signature): @@ -920,11 +956,48 @@ def test_perf_alert_summary_with_bug( ) +@pytest.fixture +def test_perf_datum(test_repository, test_perf_signature, test_job_2): + push = th_models.Push.objects.get(id=1) + perf_models.PerformanceDatum.objects.create( + repository=test_repository, + job=test_job_2, + push_id=1, + signature=test_perf_signature, + value=1, + push_timestamp=push.time, + ) + + +@pytest.fixture +def test_perf_datum_2(test_repository, test_perf_signature, test_job_3): + push = th_models.Push.objects.get(id=2) + perf_models.PerformanceDatum.objects.create( + repository=test_repository, + job=test_job_3, + push_id=2, + signature=test_perf_signature, + value=1, + push_timestamp=push.time, + ) + + @pytest.fixture def test_perf_alert(test_perf_signature, test_perf_alert_summary) -> perf_models.PerformanceAlert: return create_perf_alert(summary=test_perf_alert_summary, series_signature=test_perf_signature) +@pytest.fixture +def test_perf_alert_with_tcmetadata(test_perf_signature, test_perf_alert_summary) -> perf_models.PerformanceAlert: + perf_alert = create_perf_alert(summary=test_perf_alert_summary, series_signature=test_perf_signature) + perf_alert.taskcluster_metadata = { + "current_push": test_taskcluster_metadata_2, + "prev_push": test_taskcluster_metadata, + } + perf_alert.save() + return perf_alert + + def create_perf_alert(**alert_properties) -> perf_models.PerformanceAlert: defaults = dict( amount_abs=50.0, diff --git a/tests/webapp/api/test_performance_alerts_api.py b/tests/webapp/api/test_performance_alerts_api.py index fe976a9715c..fd94b903c5c 100644 --- a/tests/webapp/api/test_performance_alerts_api.py +++ b/tests/webapp/api/test_performance_alerts_api.py @@ -8,7 +8,14 @@ from treeherder.perf.models import PerformanceAlert, PerformanceAlertSummary, PerformanceFramework -def test_alerts_get(client, test_repository, test_perf_alert): +def test_alerts_get( + client, + test_repository, + test_perf_alert_with_tcmetadata, + test_perf_datum, + test_taskcluster_metadata, + test_taskcluster_metadata_2, +): resp = client.get(reverse('performance-alerts-list')) assert resp.status_code == 200 @@ -27,6 +34,7 @@ def test_alerts_get(client, test_repository, test_perf_alert): 'prev_value', 'related_summary_id', 'series_signature', + 'taskcluster_metadata', 'summary_id', 'status', 't_value', @@ -36,6 +44,20 @@ def test_alerts_get(client, test_repository, test_perf_alert): 'noise_profile', } assert resp.json()['results'][0]['related_summary_id'] is None + assert set(resp.json()['results'][0]['taskcluster_metadata'].keys()) == { + 'current_push', + 'prev_push', + } + assert set(resp.json()['results'][0]['taskcluster_metadata']['current_push'].keys()) == { + 'task_id', + 'retry_id', + 'result', + } + assert set(resp.json()['results'][0]['taskcluster_metadata']['prev_push'].keys()) == { + 'task_id', + 'retry_id', + 'result', + } def test_alerts_put( diff --git a/tests/webapp/api/test_performance_alertsummary_api.py b/tests/webapp/api/test_performance_alertsummary_api.py index 69374065845..a288fc48f2b 100644 --- a/tests/webapp/api/test_performance_alertsummary_api.py +++ b/tests/webapp/api/test_performance_alertsummary_api.py @@ -53,7 +53,14 @@ def test_perf_alert_onhold(test_perf_signature, test_perf_alert_summary_onhold) ) -def test_alert_summaries_get(client, test_perf_alert_summary, test_perf_alert): +def test_alert_summaries_get( + client, + test_perf_alert_summary, + test_perf_alert_with_tcmetadata, + test_perf_datum, + test_taskcluster_metadata, + test_taskcluster_metadata_2 +): # verify that we get the performance summary + alert on GET resp = client.get(reverse('performance-alert-summaries-list')) assert resp.status_code == 200 @@ -89,6 +96,7 @@ def test_alert_summaries_get(client, test_perf_alert_summary, test_perf_alert): 'id', 'status', 'series_signature', + 'taskcluster_metadata', 'is_regression', 'starred', 'manually_created', @@ -105,12 +113,29 @@ def test_alert_summaries_get(client, test_perf_alert_summary, test_perf_alert): 'noise_profile', } assert resp.json()['results'][0]['related_alerts'] == [] + assert set(resp.json()['results'][0]['alerts'][0]['taskcluster_metadata'].keys()) == { + 'current_push', + 'prev_push', + } + assert set(resp.json()['results'][0]['alerts'][0]['taskcluster_metadata']['current_push'].keys()) == { + 'task_id', + 'retry_id', + 'result', + } + assert set(resp.json()['results'][0]['alerts'][0]['taskcluster_metadata']['prev_push'].keys()) == { + 'task_id', + 'retry_id', + 'result', + } def test_alert_summaries_get_onhold( client, test_perf_alert_summary, - test_perf_alert, + test_perf_alert_with_tcmetadata, + test_perf_datum, + test_taskcluster_metadata, + test_taskcluster_metadata_2, test_perf_alert_summary_onhold, test_perf_alert_onhold, test_repository_onhold, @@ -150,6 +175,7 @@ def test_alert_summaries_get_onhold( 'id', 'status', 'series_signature', + 'taskcluster_metadata', 'is_regression', 'starred', 'manually_created', @@ -166,6 +192,20 @@ def test_alert_summaries_get_onhold( 'noise_profile', } assert resp.json()['results'][0]['related_alerts'] == [] + assert set(resp.json()['results'][0]['alerts'][0]['taskcluster_metadata'].keys()) == { + 'current_push', + 'prev_push', + } + assert set(resp.json()['results'][0]['alerts'][0]['taskcluster_metadata']['current_push'].keys()) == { + 'task_id', + 'retry_id', + 'result', + } + assert set(resp.json()['results'][0]['alerts'][0]['taskcluster_metadata']['prev_push'].keys()) == { + 'task_id', + 'retry_id', + 'result', + } def test_alert_summaries_put( diff --git a/treeherder/webapp/api/performance_serializers.py b/treeherder/webapp/api/performance_serializers.py index c48a056cf6d..d428f6ce074 100644 --- a/treeherder/webapp/api/performance_serializers.py +++ b/treeherder/webapp/api/performance_serializers.py @@ -5,7 +5,7 @@ from django.db import transaction from rest_framework import exceptions, serializers -from treeherder.model.models import Repository +from treeherder.model.models import Repository, TaskclusterMetadata, Job from treeherder.perf.models import ( BackfillRecord, IssueTracker, @@ -17,6 +17,7 @@ PerformanceSignature, PerformanceTag, ) +from treeherder.webapp.api.serializers import TaskclusterMetadataSerializer from treeherder.webapp.api.utils import to_timestamp @@ -116,8 +117,53 @@ class Meta: ] +class AlertTaskclusterMetadataSerializer(serializers.ModelSerializer): + current_push = serializers.SerializerMethodField() + prev_push = serializers.SerializerMethodField() + + def get_current_push(self, alert): + datum = PerformanceDatum.objects.filter( + signature=alert.series_signature, + repository=alert.series_signature.repository, + push_id=alert.summary.push + ).first() + if datum: + metadata = TaskclusterMetadata.objects.get(job=datum.job) + return { + 'task_id': metadata.task_id, + 'retry_id': metadata.retry_id, + } + else: + return {} + + def get_prev_push(self, alert): + datum = PerformanceDatum.objects.filter( + signature=alert.series_signature, + repository=alert.series_signature.repository, + push_id=alert.summary.prev_push + ).first() + if datum: + metadata = TaskclusterMetadata.objects.get(job=datum.job) + return { + 'task_id': metadata.task_id, + 'retry_id': metadata.retry_id, + } + else: + return {} + + class Meta: + model = Job + fields = [ + # 'task_id', + # 'retry_id', + 'current_push', + 'prev_push', + ] + + class PerformanceAlertSerializer(serializers.ModelSerializer): series_signature = PerformanceSignatureSerializer(read_only=True) + taskcluster_metadata = serializers.SerializerMethodField() summary_id = serializers.SlugRelatedField( slug_field="id", source="summary", @@ -183,6 +229,10 @@ def update(self, instance, validated_data): return super().update(instance, validated_data) + def get_taskcluster_metadata(self, alert): + serializer = AlertTaskclusterMetadataSerializer(alert) + return serializer.data + def get_classifier_email(self, performance_alert): return getattr(performance_alert.classifier, 'email', None) @@ -192,6 +242,7 @@ class Meta: 'id', 'status', 'series_signature', + 'taskcluster_metadata', 'is_regression', 'prev_value', 'new_value', diff --git a/ui/perfherder/alerts/AlertActionPanel.jsx b/ui/perfherder/alerts/AlertActionPanel.jsx index 9b414d2eb6a..ac88f157204 100644 --- a/ui/perfherder/alerts/AlertActionPanel.jsx +++ b/ui/perfherder/alerts/AlertActionPanel.jsx @@ -140,6 +140,7 @@ export default class AlertActionPanel extends React.Component { } else { fetchAlertSummaries(alertSummary.id); } + this.clearSelectedAlerts(); };