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 82a571e078b..1e5711fb8c8 100644 --- a/tests/webapp/api/test_performance_alertsummary_api.py +++ b/tests/webapp/api/test_performance_alertsummary_api.py @@ -95,6 +95,7 @@ def test_alert_summaries_get( "prev_push_revision", "original_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()) == { @@ -176,6 +177,7 @@ def test_alert_summaries_get_onhold( "prev_push_revision", "original_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 9e68e467f82..e34cb83e1e1 100644 --- a/treeherder/webapp/api/performance_serializers.py +++ b/treeherder/webapp/api/performance_serializers.py @@ -300,6 +300,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) @@ -317,6 +318,15 @@ 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, framework=performance_alert_summary.framework + ) + .exclude(id=performance_alert_summary.id) + .values_list("id", flat=True) + ) + class Meta: model = PerformanceAlertSummary fields = [ @@ -344,6 +354,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 39b1240739d..3d722dab894 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, @@ -230,6 +231,24 @@ const AlertHeader = ({ Revisions have been modified. )} + {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 && (