3
3
// BSD-style license that can be found in the LICENSE file.
4
4
5
5
import 'dart:async' ;
6
+ import 'dart:collection' ;
6
7
7
- import 'package:flutter/material.dart' ;
8
+ import 'package:flutter/foundation.dart' ;
9
+ import 'package:flutter_current_results/results.dart' ;
8
10
import 'package:http/http.dart' as http;
9
11
import 'dart:convert' ;
10
12
@@ -17,111 +19,171 @@ const String apiHost = 'current-results-qvyo5rktwa-uc.a.run.app';
17
19
const int fetchLimit = 3000 ;
18
20
const int maxFetchedResults = 100 * fetchLimit;
19
21
20
- class QueryResults extends ChangeNotifier {
21
- Filter filter = Filter ('' );
22
- StreamSubscription <GetResultsResponse >? fetcher;
23
- List <String > names = [];
24
- Map <String , Counts > counts = {};
25
- Map <String , Map <ChangeInResult , List <Result >>> grouped = {};
22
+ abstract class QueryResultsBase extends ChangeNotifier {
23
+ Filter _filter;
24
+ StreamSubscription <Iterable <(ChangeInResult , Result )>>? _streamFetcher;
25
+ bool get isDone => _streamFetcher == null ;
26
+ final bool supportsEmptyQuery;
27
+
28
+ SplayTreeMap <String , Counts > counts = SplayTreeMap ();
29
+ SplayTreeMap <String , SplayTreeMap <ChangeInResult , List <Result >>> grouped =
30
+ SplayTreeMap ();
26
31
TestCounts testCounts = TestCounts ();
27
32
Counts resultCounts = Counts ();
28
33
int fetchedResultsCount = 0 ;
29
- bool get noQuery => filter.terms.isEmpty;
30
-
31
- QueryResults ();
32
34
33
- void fetch (Filter newFilter) {
34
- if (filter != newFilter) {
35
- filter = newFilter;
36
- fetchCurrentResults ();
35
+ QueryResultsBase (
36
+ this ._filter, {
37
+ bool fetchInitialResults = false ,
38
+ this .supportsEmptyQuery = false ,
39
+ }) {
40
+ if (fetchInitialResults) {
41
+ _fetchResults ();
37
42
}
38
43
}
39
44
40
- @override
41
- void dispose () {
42
- fetcher ? . cancel ();
43
- super . dispose ();
44
- }
45
+ bool get hasQuery => _filter.terms.isNotEmpty;
46
+
47
+ List < String > get names => grouped.keys. toList ();
48
+
49
+ Filter get filter => _filter;
45
50
46
- GetResultsResponse resultsObject = GetResultsResponse .create ();
51
+ set filter (Filter newFilter) {
52
+ if (_filter != newFilter) {
53
+ _filter = newFilter;
54
+ Future .microtask (fetch);
55
+ }
56
+ }
47
57
48
- void fetchCurrentResults () async {
49
- fetcher? .cancel ();
50
- fetcher = null ;
51
- names = [];
52
- counts = {};
53
- grouped = {};
58
+ void fetch () {
59
+ _streamFetcher? .cancel ();
60
+ _streamFetcher = null ;
61
+ counts.clear ();
62
+ grouped.clear ();
54
63
testCounts = TestCounts ();
55
64
resultCounts = Counts ();
56
65
fetchedResultsCount = 0 ;
57
- if (noQuery) return ;
58
- fetcher = fetchResults (filter).listen (onResults, onDone: onDone);
66
+ notifyListeners ();
67
+ if (hasQuery || supportsEmptyQuery) {
68
+ _fetchResults ();
69
+ }
59
70
}
60
71
61
- void onResults (GetResultsResponse response) {
62
- final results = response.results;
63
- fetchedResultsCount += results.length;
64
- if (fetchedResultsCount >= maxFetchedResults) {
65
- fetcher? .cancel ();
66
- fetcher = null ;
67
- }
68
- for (final result in results) {
69
- final change = ChangeInResult (result);
72
+ void _fetchResults () {
73
+ _streamFetcher = createResultsStream ().listen (
74
+ _processResults,
75
+ onDone: () {
76
+ _streamFetcher = null ;
77
+ notifyListeners ();
78
+ },
79
+ );
80
+ }
81
+
82
+ @visibleForOverriding
83
+ Stream <Iterable <(ChangeInResult , Result )>> createResultsStream ();
84
+
85
+ void _processResults (Iterable <(ChangeInResult , Result )> results) {
86
+ for (final (change, result) in results) {
70
87
grouped
71
- .putIfAbsent (result.name, () => < ChangeInResult , List < Result > > {} )
72
- .putIfAbsent (change, () => < Result > [])
88
+ .putIfAbsent (result.name, SplayTreeMap . new )
89
+ .putIfAbsent (change, () => [])
73
90
.add (result);
74
91
counts.putIfAbsent (result.name, () => Counts ()).addResult (change, result);
75
92
testCounts.addResult (change, result);
76
93
resultCounts.addResult (change, result);
77
94
}
78
- names = grouped.keys.toList ()..sort ();
79
95
notifyListeners ();
80
96
}
81
97
82
- void onDone () {
83
- fetcher = null ;
98
+ @override
99
+ void dispose () {
100
+ _streamFetcher? .cancel ();
101
+ super .dispose ();
84
102
}
85
103
}
86
104
87
- Stream <GetResultsResponse > fetchResults (Filter filter) async * {
88
- final client = http.Client ();
89
- var pageToken = '' ;
90
- do {
91
- final resultsQuery = Uri .https (apiHost, 'v1/results' , {
92
- 'filter' : filter.terms.join (',' ),
93
- 'pageSize' : '$fetchLimit ' ,
94
- 'pageToken' : pageToken,
95
- });
96
- final response = await client.get (resultsQuery);
97
- final results = GetResultsResponse .create ()
98
- ..mergeFromProto3Json (json.decode (response.body));
99
- yield results;
100
- pageToken = results.nextPageToken;
101
- } while (pageToken.isNotEmpty);
105
+ class QueryResults extends QueryResultsBase {
106
+ final http.Client _client;
107
+
108
+ QueryResults (super .filter, {http.Client ? client})
109
+ : _client = client ?? http.Client ();
110
+
111
+ @override
112
+ Stream <Iterable <(ChangeInResult , Result )>> createResultsStream () {
113
+ return _streamPagedResults ().transform (
114
+ StreamTransformer .fromHandlers (
115
+ handleData: (response, sink) {
116
+ fetchedResultsCount += response.results.length;
117
+ sink.add (
118
+ response.results.map ((result) => (ChangeInResult (result), result)),
119
+ );
120
+ if (fetchedResultsCount >= maxFetchedResults) {
121
+ sink.close ();
122
+ }
123
+ },
124
+ ),
125
+ );
126
+ }
127
+
128
+ Stream <GetResultsResponse > _streamPagedResults () async * {
129
+ var pageToken = '' ;
130
+ do {
131
+ final resultsQuery = Uri .https (apiHost, 'v1/results' , {
132
+ 'filter' : filter.terms.join (',' ),
133
+ 'pageSize' : '$fetchLimit ' ,
134
+ 'pageToken' : pageToken,
135
+ });
136
+ final response = await _client.get (resultsQuery);
137
+ final results = GetResultsResponse .create ()
138
+ ..mergeFromProto3Json (json.decode (response.body));
139
+ yield results;
140
+ pageToken = results.nextPageToken;
141
+ } while (pageToken.isNotEmpty);
142
+ }
102
143
}
103
144
104
- class ChangeInResult {
105
- final String result;
106
- final String expected;
145
+ class ChangeInResult implements Comparable <ChangeInResult > {
146
+ static final _cache = < String , ChangeInResult > {};
147
+
148
+ final bool matches;
107
149
final bool flaky;
108
150
final String text;
109
151
110
- bool get matches => result == expected;
111
-
112
- String get kind => flaky
113
- ? 'flaky'
152
+ ResultKind get kind => flaky
153
+ ? ResultKind .flaky
114
154
: matches
115
- ? ' pass'
116
- : ' fail' ;
155
+ ? ResultKind . pass
156
+ : ResultKind . fail;
117
157
118
- ChangeInResult (Result result)
119
- : this ._(result.result, result.expected, result.flaky);
158
+ factory ChangeInResult (Result result) {
159
+ return ChangeInResult ._create (
160
+ result: result.result,
161
+ expected: result.expected,
162
+ isFlaky: result.flaky,
163
+ );
164
+ }
120
165
121
- ChangeInResult ._(this .result, this .expected, this .flaky)
122
- : text = flaky
123
- ? "flaky (latest result $result expected $expected )"
124
- : "$result (expected $expected )" ;
166
+ factory ChangeInResult ._create ({
167
+ required String result,
168
+ required String expected,
169
+ required bool isFlaky,
170
+ }) {
171
+ final bool matches = result == expected;
172
+ final String text;
173
+
174
+ if (isFlaky) {
175
+ text = 'flaky (latest result $result expected $expected )' ;
176
+ } else {
177
+ text = matches ? result : '$result (expected $expected )' ;
178
+ }
179
+
180
+ return _cache.putIfAbsent (
181
+ text,
182
+ () => ChangeInResult ._(text, matches, isFlaky),
183
+ );
184
+ }
185
+
186
+ ChangeInResult ._(this .text, this .matches, this .flaky);
125
187
126
188
@override
127
189
String toString () => text;
@@ -132,6 +194,18 @@ class ChangeInResult {
132
194
133
195
@override
134
196
int get hashCode => text.hashCode;
197
+
198
+ @override
199
+ int compareTo (ChangeInResult other) {
200
+ if (matches != other.matches) {
201
+ return matches ? 1 : - 1 ;
202
+ }
203
+
204
+ if (flaky != other.flaky) {
205
+ return flaky ? - 1 : 1 ;
206
+ }
207
+ return text.compareTo (other.text);
208
+ }
135
209
}
136
210
137
211
String resultAsCommaSeparated (Result result) => [
0 commit comments