[results feed] Sort result groups to put failing results at the top

Add horizontal rule between result groups.

Change-Id: Ic713b1b968c4c57953f441e42b0e2504c667a1c0
Reviewed-on: https://dart-review.googlesource.com/c/dart_ci/+/132324
Reviewed-by: Alexander Thomas <athom@google.com>
diff --git a/results_feed/lib/src/components/results_panel.html b/results_feed/lib/src/components/results_panel.html
index 40d3705..b6d4015 100644
--- a/results_feed/lib/src/components/results_panel.html
+++ b/results_feed/lib/src/components/results_panel.html
@@ -1,4 +1,4 @@
-<div *ngFor="let configurationGroup of changes">
+<div *ngFor="let configurationGroup of changes; let last=last">
   <material-chips>
     <template
         ngFor
@@ -67,4 +67,5 @@
       &vellip; {{resultGroup.length - resultLimit}} more changed results
     </div>
   </div>
+  <hr *ngIf="!last">
 </div>
\ No newline at end of file
diff --git a/results_feed/lib/src/components/try_results_component.dart b/results_feed/lib/src/components/try_results_component.dart
index 0dcf1f1..ff1cd31 100644
--- a/results_feed/lib/src/components/try_results_component.dart
+++ b/results_feed/lib/src/components/try_results_component.dart
@@ -157,8 +157,7 @@
       "are failing on configurations",
       "```",
       ...{
-        for (final change in failures)
-          ...change.configurations.configurations
+        for (final change in failures) ...change.configurations.configurations
       },
       "```"
     ].join('\n');
diff --git a/results_feed/lib/src/model/commit.dart b/results_feed/lib/src/model/commit.dart
index 90a1524..0b4070d 100644
--- a/results_feed/lib/src/model/commit.dart
+++ b/results_feed/lib/src/model/commit.dart
@@ -216,12 +216,13 @@
   final List<List<List<Change>>> changes;
 
   Changes(Iterable<Change> newChanges)
-      : this.grouped(
-            group(newChanges, (Change change) => change.configurations));
+      : this.grouped(failuresFirst(
+            group(newChanges, (Change change) => change.configurations)));
 
   Changes.active(Iterable<Change> newChanges)
-      : this.grouped(group(newChanges.where((change) => change.active),
-            (Change change) => change.activeConfigurations));
+      : this.grouped(failuresFirst(group(
+            newChanges.where((change) => change.active),
+            (Change change) => change.activeConfigurations)));
 
   Changes.grouped(this.changes);
 
@@ -251,6 +252,27 @@
     ];
   }
 
+  static List<List<List<Change>>> failuresFirst(
+      List<List<List<Change>>> unsorted) {
+    bool resultGroupIsFailure(List<Change> group) =>
+        group.first.resultStyle == 'failure';
+    bool resultGroupIsSuccess(List<Change> group) =>
+        group.first.resultStyle == 'success';
+    bool configurationGroupHasFailure(List<List<Change>> group) =>
+        group.any(resultGroupIsFailure);
+    bool configurationGroupHasNoFailures(List<List<Change>> group) =>
+        group.every(resultGroupIsSuccess);
+
+    return [
+      for (final configurationGroup in unsorted)
+        if (configurationGroupHasFailure(configurationGroup))
+          [
+            ...configurationGroup.where(resultGroupIsFailure),
+            ...configurationGroup.where(resultGroupIsSuccess),
+          ]
+    ]..addAll(unsorted.where(configurationGroupHasNoFailures));
+  }
+
   static bool empty(x, f) => x.isEmpty;
 
   Changes filtered(Filter filter) {