blob: 2f8b457e05884ef7ed5b1a005f8658e1227edcea [file] [log] [blame]
// Copyright (c) 2019, 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:math' show max, min;
import 'package:firebase_functions_interop/firebase_functions_interop.dart';
import 'firestore.dart';
void info(Object message) {
print("Info: $message");
}
void error(Object message) {
print("Error: $message");
}
// Cloud functions run the cloud function many times in the same isolate.
// Use static initializer to run global initialization once.
Firestore firestore = createFirestore();
Firestore createFirestore() {
final app = FirebaseAdmin.instance.initializeApp();
return app.firestore()
..settings(FirestoreSettings(timestampsInSnapshots: true));
}
class FirestoreServiceImpl implements FirestoreService {
Future<bool> hasPatchset(String review, String patchset) => firestore
.document('reviews/$review/patchsets/$patchset')
.get()
.then((s) => s.exists);
Future<Map<String, dynamic>> getCommit(String hash) => firestore
.document('commits/$hash')
.get()
.then((s) => s.exists ? s.data.toMap() : null);
Future<Map<String, dynamic>> getLastCommit(String hash) async {
QuerySnapshot lastCommit = await firestore
.collection('commits')
.orderBy('index', descending: true)
.limit(1)
.get();
final result = lastCommit.documents.first.data.toMap();
result['id'] = lastCommit.documents.first.documentID;
return result;
}
Future<void> addCommit(String id, Map<String, dynamic> data) async {
data['created'] = Timestamp.fromDateTime(data['created']);
final docRef = firestore.document('commits/$id');
await docRef.setData(DocumentData.fromMap(data));
}
Future<void> updateConfiguration(String configuration, String builder) async {
final record =
await firestore.document('configurations/$configuration').get();
if (!record.exists || record.data.getString('builder') != builder) {
await firestore
.document('configurations/$configuration')
.setData(DocumentData.fromMap({'builder': builder}));
if (!record.exists) {
info('Configuration document $configuration -> $builder created');
} else {
info('Configuration document changed: $configuration -> $builder '
'(was ${record.data.getString("builder")}');
}
}
}
Future<void> updateBuildInfo(
String builder, int buildNumber, int index) async {
final documentRef = firestore.document('builds/$builder:$index');
final record = await documentRef.get();
if (!record.exists) {
await documentRef.setData(DocumentData.fromMap(
{'builder': builder, 'build_number': buildNumber, 'index': index}));
info('Created build record: '
'builder: $builder, build_number: $buildNumber, index: $index');
} else if (record.data.getInt('index') != index) {
error(
'Build $buildNumber of $builder had commit index ${record.data.getInt('index')},'
'should be $index.');
}
}
Future<void> storeChange(
Map<String, dynamic> change, int startIndex, int endIndex) async {
String name = change['name'];
String result = change['result'];
String previousResult = change['previous_result'] ?? 'new test';
QuerySnapshot snapshot = await firestore
.collection('results')
.orderBy('blamelist_end_index', descending: true)
.where('name', isEqualTo: name)
.where('result', isEqualTo: result)
.where('previous_result', isEqualTo: previousResult)
.where('expected', isEqualTo: change['expected'])
.limit(5) // We will pick the right one, probably the latest.
.get();
// Find an existing change group with a blamelist that intersects this change.
bool blamelistIncludesChange(DocumentSnapshot groupDocument) {
var group = groupDocument.data;
var groupStart = group.getInt('blamelist_start_index');
var groupEnd = group.getInt('blamelist_end_index');
return startIndex <= groupEnd && endIndex >= groupStart;
}
DocumentSnapshot group = snapshot.documents
.firstWhere(blamelistIncludesChange, orElse: () => null);
if (group == null) {
info("Adding group for $name");
return firestore.collection('results').add(DocumentData.fromMap({
'name': name,
'result': result,
'previous_result': previousResult,
'expected': change['expected'],
'blamelist_start_index': startIndex,
'blamelist_end_index': endIndex,
'trivial_blamelist': startIndex == endIndex,
'configurations': <String>[change['configuration']]
}));
}
// Update the change group in a transaction.
// Add new configuration and narrow the blamelist.
Future<void> updateGroup(Transaction transaction) async {
final DocumentSnapshot groupSnapshot =
await transaction.get(group.reference);
final data = groupSnapshot.data;
final newStart = max(startIndex, data.getInt('blamelist_start_index'));
final newEnd = min(endIndex, data.getInt('blamelist_end_index'));
final updateMap = <String, dynamic>{
'blamelist_start_index':
max(startIndex, data.getInt('blamelist_start_index')),
'blamelist_end_index': min(endIndex, data.getInt('blamelist_end_index'))
};
updateMap['trivial_blamelist'] = (updateMap['blamelist_start_index'] ==
updateMap['blamelist_end_index']);
final update = UpdateData.fromMap({
'blamelist_start_index': newStart,
'blamelist_end_index': newEnd,
'trivial_blamelist': newStart == newEnd
});
update.setFieldValue('configurations',
Firestore.fieldValues.arrayUnion([change['configuration']]));
group.reference.updateData(update);
}
return firestore.runTransaction(updateGroup);
}
Future<void> storeTryChange(
Map<String, dynamic> change, int review, int patchset) async {
String name = change['name'];
String result = change['result'];
String expected = change['expected'];
String reviewPath = change['commit_hash'];
String previousResult = change['previous_result'] ?? 'new test';
QuerySnapshot snapshot = await firestore
.collection('try_results')
.where('review_path', isEqualTo: reviewPath)
.where('name', isEqualTo: name)
.where('result', isEqualTo: result)
.where('previous_result', isEqualTo: previousResult)
.where('expected', isEqualTo: expected)
.limit(1)
.get();
if (snapshot.isEmpty) {
info("Adding group for $name");
return firestore.collection('try_results').add(DocumentData.fromMap({
'name': name,
'result': result,
'previous_result': previousResult,
'expected': expected,
'review_path': reviewPath,
'review': review,
'patchset': patchset,
'configurations': <String>[change['configuration']]
}));
} else {
final update = UpdateData()
..setFieldValue('configurations',
Firestore.fieldValues.arrayUnion([change['configuration']]));
snapshot.documents.first.reference.updateData(update);
}
}
Future<void> storeReview(String review, Map<String, dynamic> data) =>
firestore.document('reviews/$review').setData(DocumentData.fromMap(data));
Future<void> storePatchset(
String review, String patchset, Map<String, dynamic> data) =>
firestore
.document('reviews/$review/patchsets/$patchset')
.setData(DocumentData.fromMap(data));
}