[current results ui] Add summary counts of failing and flaky tests
Change-Id: I9ed901863cb3b4125445eda93fd64dec2cc70189
Reviewed-on: https://dart-review.googlesource.com/c/dart_ci/+/164820
Reviewed-by: Alexander Thomas <athom@google.com>
diff --git a/current_results_ui/lib/main.dart b/current_results_ui/lib/main.dart
index 25058d1..4306d9b 100644
--- a/current_results_ui/lib/main.dart
+++ b/current_results_ui/lib/main.dart
@@ -84,6 +84,8 @@
),
),
persistentFooterButtons: [
+ const TestSummary(),
+ const ResultsSummary(),
const ApiPortalLink(),
const JsonLink(),
const TextPopup(),
diff --git a/current_results_ui/lib/query.dart b/current_results_ui/lib/query.dart
index ee66120..9acc661 100644
--- a/current_results_ui/lib/query.dart
+++ b/current_results_ui/lib/query.dart
@@ -22,8 +22,10 @@
final Filter filter;
StreamSubscription<GetResultsResponse> fetcher;
List<String> names = [];
- Map<String, Map<String, int>> counts = {};
+ Map<String, Counts> counts = {};
Map<String, Map<ChangeInResult, List<Result>>> grouped = {};
+ TestCounts testCounts = TestCounts();
+ Counts resultCounts = Counts();
int fetchedResultsCount = 0;
bool get noQuery => filter.terms.isEmpty;
@@ -39,11 +41,14 @@
void fetchCurrentResults() async {
fetcher?.cancel();
+ fetcher = null;
names = [];
counts = {};
grouped = {};
+ testCounts = TestCounts();
+ resultCounts = Counts();
fetchedResultsCount = 0;
-
+ if (noQuery) return;
fetcher = fetchResults(filter).listen(onResults, onDone: onDone);
}
@@ -60,10 +65,9 @@
.putIfAbsent(result.name, () => <ChangeInResult, List<Result>>{})
.putIfAbsent(change, () => <Result>[])
.add(result);
- counts
- .putIfAbsent(result.name, () => <String, int>{})
- .putIfAbsent(change.kind, () => 0);
- ++counts[result.name][change.kind];
+ counts.putIfAbsent(result.name, () => Counts()).addResult(change, result);
+ testCounts.addResult(change, result);
+ resultCounts.addResult(change, result);
}
names = grouped.keys.toList()..sort();
notifyListeners();
@@ -129,3 +133,49 @@
].join(',');
String resultTextHeader = "name,configuration,result,expected,flaky,timeMs";
+
+class Counts {
+ int count = 0;
+ int countFailing = 0;
+ int countFlaky = 0;
+
+ int get countPassing => count - countFailing - countFlaky;
+
+ void addResult(ChangeInResult change, Result result) {
+ count++;
+ if (change.flaky) {
+ countFlaky++;
+ } else if (!change.matches) {
+ countFailing++;
+ }
+ }
+}
+
+class TestCounts extends Counts {
+ String currentTest = '';
+ bool currentFailing = false;
+ bool currentFlaky = false;
+
+ void addResult(ChangeInResult change, Result result) {
+ if (currentTest != result.name) {
+ if (currentTest.compareTo(result.name) > 0) {
+ print('Results are not sorted by test name: '
+ '$currentTest, ${result.name}');
+ return;
+ }
+ currentFlaky = false;
+ currentFailing = false;
+ currentTest = result.name;
+ count++;
+ }
+ if (change.flaky) {
+ if (!currentFlaky) {
+ currentFlaky = true;
+ countFlaky++;
+ }
+ } else if (!change.matches && !currentFailing) {
+ currentFailing = true;
+ countFailing++;
+ }
+ }
+}
diff --git a/current_results_ui/lib/results.dart b/current_results_ui/lib/results.dart
index e07dd46..902f371 100644
--- a/current_results_ui/lib/results.dart
+++ b/current_results_ui/lib/results.dart
@@ -7,7 +7,9 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
+import 'package:provider/provider.dart';
+import 'src/generated/query.pb.dart';
import 'query.dart';
const Color lightCoral = Color.fromARGB(255, 240, 128, 128);
@@ -17,7 +19,6 @@
'flaky': gold,
'fail': lightCoral,
};
-const kinds = ['pass', 'fail', 'flaky'];
class ResultsPanel extends StatelessWidget {
final QueryResults queryResults;
@@ -49,8 +50,8 @@
class ExpandableResult extends StatefulWidget {
final String name;
- final changeGroups;
- final counts;
+ final Map<ChangeInResult, List<Result>> changeGroups;
+ final Counts counts;
final bool showAll;
ExpandableResult(this.name, this.changeGroups, this.counts, this.showAll)
@@ -60,6 +61,26 @@
_ExpandableResultState createState() => _ExpandableResultState();
}
+class CountItem {
+ String text;
+ Color color;
+
+ CountItem(int count, this.color) {
+ if (count > 0) {
+ text = count.toString();
+ } else {
+ color = Colors.transparent;
+ text = '';
+ }
+ }
+}
+
+List<CountItem> countItems(Counts counts) => [
+ CountItem(counts.countPassing, resultColors['pass']),
+ CountItem(counts.countFailing, resultColors['fail']),
+ CountItem(counts.countFlaky, resultColors['flaky'])
+ ];
+
class _ExpandableResultState extends State<ExpandableResult> {
bool expanded = false;
@@ -80,19 +101,16 @@
icon: Icon(expanded ? Icons.expand_less : Icons.expand_more),
onPressed: () => setState(() => expanded = !expanded),
),
- for (final kind in kinds)
+ for (final item in countItems(widget.counts))
Container(
width: 24,
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 1.0),
decoration: BoxDecoration(
- color: widget.counts.containsKey(kind)
- ? resultColors[kind]
- : Colors.transparent,
+ color: item.color,
shape: BoxShape.circle,
),
- child: Text('${widget.counts[kind] ?? ''}',
- style: TextStyle(fontSize: 14.0)),
+ child: Text(item.text, style: TextStyle(fontSize: 14.0)),
),
Expanded(
flex: 1,
@@ -204,3 +222,81 @@
]);
}
}
+
+class ResultsSummary extends StatelessWidget {
+ const ResultsSummary() : super();
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer<QueryResults>(
+ builder: (context, results, child) =>
+ Summary("results", results.resultCounts),
+ );
+ }
+}
+
+class TestSummary extends StatelessWidget {
+ const TestSummary() : super();
+
+ @override
+ Widget build(BuildContext context) {
+ return Consumer<QueryResults>(
+ builder: (context, results, child) =>
+ Summary("tests", results.testCounts),
+ );
+ }
+}
+
+class Summary extends StatelessWidget {
+ final String typeText;
+ final Counts counts;
+
+ Summary(this.typeText, this.counts);
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ alignment: AlignmentDirectional.centerStart,
+ width: 200.0,
+ height: 36.0,
+ child: Row(
+ children: [
+ Text(
+ typeText,
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ Pill(Colors.black26, counts.count, 'total'),
+ Pill(resultColors['fail'], counts.countFailing, 'failing'),
+ Pill(resultColors['flaky'], counts.countFlaky, 'flaky'),
+ ],
+ ),
+ );
+ }
+}
+
+class Pill extends StatelessWidget {
+ final Color color;
+ final int count;
+ final String tooltip;
+
+ Pill(this.color, this.count, this.tooltip);
+
+ @override
+ Widget build(BuildContext context) {
+ return Tooltip(
+ message: tooltip,
+ child: Container(
+ //width: 24,
+ height: 24,
+ alignment: Alignment.center,
+ margin: EdgeInsets.symmetric(horizontal: 4.0),
+ padding: EdgeInsets.symmetric(horizontal: 8.0),
+ decoration: BoxDecoration(
+ color: color,
+ borderRadius: BorderRadius.circular(14.0),
+ ),
+ child: Text(count.toString(), style: TextStyle(fontSize: 14.0)),
+ ),
+ );
+ }
+}