Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions current_results_ui/lib/model/comment.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:cloud_firestore/cloud_firestore.dart';

class Comment {
final String id;
final String author;
final DateTime created;
final String comment;
final bool? approved;
final List<String> tryResults;

Comment({
required this.id,
required this.author,
required this.created,
required this.comment,
this.approved,
required this.tryResults,
});

factory Comment.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return Comment(
id: doc.id,
author: data['author'] ?? '',
created: (data['created'] as Timestamp).toDate(),
comment: data['comment'] ?? '',
approved: data['approved'],
tryResults: List<String>.from(data['try_results'] ?? []),
);
}
}
46 changes: 46 additions & 0 deletions current_results_ui/lib/model/review.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:cloud_firestore/cloud_firestore.dart';

class Review {
final String id;
final String subject;
final List<Patchset> patchsets;

Review({required this.id, required this.subject, required this.patchsets});

factory Review.fromFirestore(DocumentSnapshot doc, List<Patchset> patchsets) {
final data = doc.data() as Map<String, dynamic>;
return Review(
id: doc.id,
subject: data['subject'] ?? '',
patchsets: patchsets,
);
}
}

class Patchset {
final String id;
final String description;
final int number;
final int patchsetGroup;

Patchset({
required this.id,
required this.description,
required this.number,
required this.patchsetGroup,
});

factory Patchset.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return Patchset(
id: doc.id,
description: data['description'] ?? '',
number: data['number'] ?? 0,
patchsetGroup: data['patchset_group'] ?? 0,
);
}
}
38 changes: 38 additions & 0 deletions current_results_ui/lib/model/try_build.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:cloud_firestore/cloud_firestore.dart';

class TryBuild {
final String id;
final int buildNumber;
final String builder;
final bool success;
final bool completed;
final int review;
final int patchset;

TryBuild({
required this.id,
required this.buildNumber,
required this.builder,
required this.success,
required this.completed,
required this.review,
required this.patchset,
});

factory TryBuild.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return TryBuild(
id: doc.id,
buildNumber: data['build_number'] ?? 0,
builder: data['builder'] ?? '',
success: data['success'] ?? false,
completed: data['completed'] ?? false,
review: data['review'] ?? 0,
patchset: data['patchset'] ?? 0,
);
}
}
22 changes: 19 additions & 3 deletions current_results_ui/lib/query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:collection';

import 'package:flutter/foundation.dart';

import 'package:flutter_current_results/results.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
Expand Down Expand Up @@ -156,25 +157,40 @@ class ChangeInResult implements Comparable<ChangeInResult> {
: ResultKind.fail;

factory ChangeInResult(Result result) {
return ChangeInResult._create(
return ChangeInResult.create(
result: result.result,
expected: result.expected,
isFlaky: result.flaky,
);
}

factory ChangeInResult._create({
factory ChangeInResult.create({
required String result,
required String expected,
required bool isFlaky,
String? previousResult,
}) {
final bool matches = result == expected;
final String text;

if (isFlaky) {
text = 'flaky (latest result $result expected $expected)';
} else {
text = matches ? result : '$result (expected $expected)';
final String resultText = matches
? result
: '$result (expected $expected)';

if (previousResult != null) {
if (previousResult.isNotEmpty) {
text = previousResult == result
? resultText
: '$previousResult -> $resultText';
} else {
text = 'new test => $resultText';
}
} else {
text = resultText;
}
}

return _cache.putIfAbsent(
Expand Down
30 changes: 30 additions & 0 deletions current_results_ui/lib/src/data/try_query_results.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

import 'package:flutter_current_results/src/generated/query.pb.dart';
import 'package:flutter_current_results/src/services/results_service.dart';

import '../../filter.dart';
import '../../query.dart';

class TryQueryResults extends QueryResultsBase {
final int cl;
final int patchset;
final ResultsService _resultsService;

TryQueryResults({
required this.cl,
required this.patchset,
required Filter filter,
ResultsService? resultsService,
}) : _resultsService = resultsService ?? ResultsService(),
super(filter, fetchInitialResults: true, supportsEmptyQuery: true);

@override
Stream<Iterable<(ChangeInResult, Result)>> createResultsStream() async* {
yield await _resultsService.fetchChanges(cl, patchset);
}
}
35 changes: 35 additions & 0 deletions current_results_ui/lib/src/routing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';

import '../filter.dart';
import '../main.dart';
import '../query.dart';
import '../try_results_screen.dart';
import 'data/try_query_results.dart';

GoRouter createRouter() => GoRouter(
routes: [
Expand All @@ -21,5 +26,35 @@ GoRouter createRouter() => GoRouter(
return CurrentResultsScreen(filter: filter, initialTabIndex: tab);
},
),
GoRoute(
path: '/cl/:cl/:patchset',
builder: (context, state) {
final cl = int.tryParse(state.pathParameters['cl']!);
final patchset = int.tryParse(state.pathParameters['patchset']!);
final filter = Filter(state.uri.queryParameters['filter'] ?? '');
final tab = state.uri.queryParameters.containsKey('showAll')
? 2
: state.uri.queryParameters.containsKey('flaky')
? 1
: 0;

if (cl != null && patchset != null) {
return MultiProvider(
providers: [
ChangeNotifierProvider<TryQueryResults>(
create: (_) =>
TryQueryResults(cl: cl, patchset: patchset, filter: filter),
),
ListenableProxyProvider<TryQueryResults, QueryResultsBase>(
update: (_, tryResults, _) => tryResults,
),
],
child: TryResultsScreen(initialTabIndex: tab),
);
}
// TODO: Should create a proper error screen here.
return const Text('Invalid CL or patchset');
},
),
],
);
59 changes: 59 additions & 0 deletions current_results_ui/lib/src/services/firestore_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:cloud_firestore/cloud_firestore.dart';

class FirestoreService {
static final FirestoreService _instance = FirestoreService._internal();
factory FirestoreService() => _instance;
FirestoreService._internal();

FirebaseFirestore get firestore => FirebaseFirestore.instance;

Future<DocumentSnapshot> fetchReviewInfo(int review) async {
return firestore.doc('reviews/$review').get();
}

Future<List<DocumentSnapshot>> fetchPatchsetInfo(int review) async {
final snapshot = await firestore
.collection('reviews/$review/patchsets')
.orderBy('number')
.get();
return snapshot.docs;
}

Future<List<DocumentSnapshot>> fetchTryChanges(
int review,
int patchset,
) async {
final snapshot = await firestore
.collection('try_results')
.where('review', isEqualTo: review)
.where('patchset', isEqualTo: patchset)
.orderBy('name')
.get();
return snapshot.docs;
}

Future<List<DocumentSnapshot>> fetchTryBuilds(int review) async {
final snapshot = await firestore
.collection('try_builds')
.where('review', isEqualTo: review)
.get();
return snapshot.docs;
}

Future<List<DocumentSnapshot>> fetchCommentsForReview(int review) async {
final snapshot = await firestore
.collection('comments')
.where('review', isEqualTo: review)
.get();
return snapshot.docs;
}

Future<List<DocumentSnapshot>> fetchBuilders() async {
final snapshot = await firestore.collection('configurations').get();
return snapshot.docs;
}
}
76 changes: 76 additions & 0 deletions current_results_ui/lib/src/services/results_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:flutter_current_results/model/comment.dart';
import 'package:flutter_current_results/model/review.dart';
import 'package:flutter_current_results/model/try_build.dart';
import 'package:flutter_current_results/query.dart';
import 'package:flutter_current_results/src/generated/query.pb.dart';
import 'package:flutter_current_results/src/services/firestore_service.dart';

class ResultsService {
final FirestoreService _firestoreService = FirestoreService();

Future<Review> fetchReviewInfo(int review) async {
final patchsetDocs = await _firestoreService.fetchPatchsetInfo(review);
final patchsets = patchsetDocs
.map((d) => Patchset.fromFirestore(d))
.toList();
final reviewDoc = await _firestoreService.fetchReviewInfo(review);
if (reviewDoc.exists) {
return Review.fromFirestore(reviewDoc, patchsets);
}
return Review(
id: review.toString(),
subject: 'No results received yet for CL $review',
patchsets: [],
);
}

Future<Map<String, TryBuild>> fetchBuilds(int review, int patchset) async {
final buildDocs = await _firestoreService.fetchTryBuilds(review);
final builds = buildDocs.map((d) => TryBuild.fromFirestore(d));
return {for (var build in builds) build.builder: build};
}

Future<Iterable<(ChangeInResult, Result)>> fetchChanges(
int review,
int patchset,
) async {
final changeDocs = await _firestoreService.fetchTryChanges(
review,
patchset,
);
return changeDocs.expand((doc) {
final data = doc.data() as Map<String, dynamic>;
final result = data['result'] ?? '';
final change = ChangeInResult.create(
result: result,
expected: data['expected'] ?? '',
isFlaky: result.toLowerCase().contains('flaky'),
previousResult: data['previous_result'] ?? '',
);
final configurations = List<String>.from(data['configurations'] ?? []);
return configurations.map((configuration) {
final result = Result()
..name = data['name'] ?? ''
..configuration = configuration
..result = data['result'] ?? ''
..expected = data['expected'] ?? ''
..flaky = change.flaky;
return (change, result);
});
});
}

Future<List<Comment>> fetchComments(int review) async {
final commentDocs = await _firestoreService.fetchCommentsForReview(review);
return commentDocs.map((d) => Comment.fromFirestore(d)).toList();
}

Future<Map<String, String>> fetchBuilders() async {
final builderDocs = await _firestoreService.fetchBuilders();
return {for (var doc in builderDocs) doc.id: doc.get('builder') as String};
}
}
Loading