[results feed] Show message on tryjob results if new flaky tests cause build failures

If more than 10 new flaky failures happen on a try builder, then that
builder is marked failing. This only counts tests that are not already
seen to be flaky.

Show a message on the tryjobs results page if this is happening.

Change-Id: I16bf19ef640bd392c55a57276e652dd95d7c06aa
Reviewed-on: https://dart-review.googlesource.com/c/dart_ci/+/274460
Commit-Queue: William Hesse <whesse@google.com>
Reviewed-by: Alexander Thomas <athom@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/results_feed/lib/src/components/try_results_component.dart b/results_feed/lib/src/components/try_results_component.dart
index 638bc56..f7c6381 100644
--- a/results_feed/lib/src/components/try_results_component.dart
+++ b/results_feed/lib/src/components/try_results_component.dart
@@ -60,6 +60,7 @@
   bool updating = false;
   bool updatePending = false;
   bool _approving = false;
+  List<String> flakyConfigurations = [];
   final Set<Change> selected = {};
   String commentText;
 
@@ -149,6 +150,8 @@
       cachedPatchset = patchset;
       changeGroup =
           ChangeGroup(null, {}, comments, changes, LoadedResultsStatus());
+      flakyConfigurations =
+          changeGroup.changes.listFlakyConfigurations(threshold: 10);
     }
   }
 
diff --git a/results_feed/lib/src/components/try_results_component.html b/results_feed/lib/src/components/try_results_component.html
index a2d0c96..ebca465 100644
--- a/results_feed/lib/src/components/try_results_component.html
+++ b/results_feed/lib/src/components/try_results_component.html
@@ -7,6 +7,11 @@
   <h2 *ngIf="hasTruncatedResults" style="color:red">
     Tryjob results are truncated because there are more than
     1000 new failures or passes.</h2>
+  <h2 *ngIf="flakyConfigurations.isNotEmpty" style="color:red">
+    Try builds are failing because the following configurations have
+    more than 10 new, previously unseen, flaky failures:
+    {{flakyConfigurations.join(" ")}}
+  </h2>
   <h2 *ngIf="noChanges">No changed test results</h2>
   <results-panel *ngIf="!approving"
       [changes]="changeGroup.changes"
diff --git a/results_feed/lib/src/model/commit.dart b/results_feed/lib/src/model/commit.dart
index ceffc6c..8e34239 100644
--- a/results_feed/lib/src/model/commit.dart
+++ b/results_feed/lib/src/model/commit.dart
@@ -241,8 +241,13 @@
   bool get skipped => result == skippedResult;
   bool get success => result == expected;
   bool get failed => !flaky && !skipped && !success;
-  String get resultStyle =>
-      flaky ? 'flaky' : skipped ? 'skipped' : success ? 'success' : 'failure';
+  String get resultStyle => flaky
+      ? 'flaky'
+      : skipped
+          ? 'skipped'
+          : success
+              ? 'success'
+              : 'failure';
 }
 
 class Changes with IterableMixin<List<List<Change>>> {
@@ -350,6 +355,25 @@
     return applyFilter<Change>((c, f) => c, list, filter,
         (change, filter) => filter.showUnapprovedOnly && change.approved);
   }
+
+  List<String> listFlakyConfigurations({int threshold = 10}) {
+    final flakyConfigurations = <String, int>{};
+    for (final configurationGroup in changes) {
+      for (final resultGroup in configurationGroup) {
+        final change = resultGroup.first;
+        if (change.flaky) {
+          for (final config in change.configurations.configurations) {
+            flakyConfigurations[config] =
+                (flakyConfigurations[config] ?? 0) + resultGroup.length;
+          }
+        }
+      }
+    }
+    return [
+      for (final entry in flakyConfigurations.entries)
+        if (entry.value >= threshold) entry.key
+    ];
+  }
 }
 
 String githubNewIssueURL(Changes changes, String subject, String link) {