diff --git a/tests/ui/mock/alert_summaries.json b/tests/ui/mock/alert_summaries.json index d559855bd35..61ef457c60e 100644 --- a/tests/ui/mock/alert_summaries.json +++ b/tests/ui/mock/alert_summaries.json @@ -136,6 +136,7 @@ } ], "related_alerts": [], + "duplicated_summaries_ids": [], "status": 0, "bug_number": null, "bug_updated": null, @@ -223,6 +224,7 @@ } ], "related_alerts": [], + "duplicated_summaries_ids": [], "status": 0, "bug_number": null, "bug_updated": null, @@ -358,6 +360,7 @@ } ], "related_alerts": [], + "duplicated_summaries_ids": [], "status": 0, "bug_number": null, "bug_updated": null, diff --git a/tests/webapp/api/test_performance_alertsummary_api.py b/tests/webapp/api/test_performance_alertsummary_api.py index bc80cf84330..a892b4a913a 100644 --- a/tests/webapp/api/test_performance_alertsummary_api.py +++ b/tests/webapp/api/test_performance_alertsummary_api.py @@ -94,6 +94,7 @@ def test_alert_summaries_get( "push_timestamp", "prev_push_revision", "performance_tags", + "duplicated_summaries_ids", } assert len(resp.json()["results"][0]["alerts"]) == 1 assert set(resp.json()["results"][0]["alerts"][0].keys()) == { @@ -174,6 +175,7 @@ def test_alert_summaries_get_onhold( "push_timestamp", "prev_push_revision", "performance_tags", + "duplicated_summaries_ids", } assert len(resp.json()["results"][0]["alerts"]) == 1 assert set(resp.json()["results"][0]["alerts"][0].keys()) == { diff --git a/treeherder/webapp/api/performance_serializers.py b/treeherder/webapp/api/performance_serializers.py index 7101e78798a..d5a86b35705 100644 --- a/treeherder/webapp/api/performance_serializers.py +++ b/treeherder/webapp/api/performance_serializers.py @@ -293,6 +293,7 @@ class PerformanceAlertSummarySerializer(serializers.ModelSerializer): queryset=User.objects.all(), ) assignee_email = serializers.SerializerMethodField() + duplicated_summaries_ids = serializers.SerializerMethodField() # marking these fields as readonly, the user should not be modifying them # (after the item is first created, where we don't use this serializer # class) @@ -310,6 +311,13 @@ def update(self, instance, validated_data): def get_assignee_email(self, performance_alert_summary): return getattr(performance_alert_summary.assignee, "email", None) + def get_duplicated_summaries_ids(self, performance_alert_summary): + return ( + PerformanceAlertSummary.objects.filter(push=performance_alert_summary.push) + .exclude(id=performance_alert_summary.id) + .values_list("id", flat=True) + ) + class Meta: model = PerformanceAlertSummary fields = [ @@ -336,6 +344,7 @@ class Meta: "assignee_username", "assignee_email", "performance_tags", + "duplicated_summaries_ids", ] diff --git a/ui/perfherder/alerts/AlertHeader.jsx b/ui/perfherder/alerts/AlertHeader.jsx index 23b418f2c3d..10ffb2e3eac 100644 --- a/ui/perfherder/alerts/AlertHeader.jsx +++ b/ui/perfherder/alerts/AlertHeader.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; import { UncontrolledDropdown, DropdownMenu, @@ -240,6 +241,24 @@ const AlertHeader = ({ /> + {alertSummary.duplicated_summaries_ids.length > 0 && ( + + Duplicated summaries: + {alertSummary.duplicated_summaries_ids.map((id, index) => ( + + Alert #{id} + {alertSummary.duplicated_summaries_ids.length - 1 !== index && + ', '} + + ))} + + )} {performanceTags.length > 0 && (