Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial attempt for frontend + get-status firestore migration #3619

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions app_dart/lib/cocoon_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export 'src/request_handlers/get_build_status_badge.dart';
export 'src/request_handlers/get_release_branches.dart';
export 'src/request_handlers/get_repos.dart';
export 'src/request_handlers/get_status.dart';
export 'src/request_handlers/get_status_firestore.dart';
export 'src/request_handlers/get_green_commits.dart';
export 'src/request_handlers/github_rate_limit_status.dart';
export 'src/request_handlers/github_webhook.dart';
Expand Down
6 changes: 6 additions & 0 deletions app_dart/lib/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ Server createServer({
delegate: GetStatus(config: config),
),

'/api/public/get-status-firestore': CacheRequestHandler<Body>(
cache: cache,
config: config,
delegate: GetStatusFirestore(config: config),
),

'/api/public/get-green-commits': GetGreenCommits(config: config),

/// Record GitHub API quota usage in BigQuery.
Expand Down
13 changes: 13 additions & 0 deletions app_dart/lib/src/model/firestore/commit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ class Commit extends Document {
/// [RepositorySlug] of where this commit exists.
RepositorySlug get slug => RepositorySlug.full(repositoryPath!);

Map<String, dynamic> get facade {
return <String, dynamic>{
'DocumentName': name,
'FlutterRepositoryPath': repositoryPath,
'CreateTimestamp': createTimestamp,
'Sha': sha,
'Message': message,
'Author': author,
'Avatar': avatar,
'Branch': branch,
};
}

@override
String toString() {
final StringBuffer buf = StringBuffer()
Expand Down
16 changes: 16 additions & 0 deletions app_dart/lib/src/model/firestore/task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,22 @@ class Task extends Document {
return completedStatuses.contains(status);
}

@override
Map<String, dynamic> toJson() {
return <String, dynamic>{
'DocumentName': name,
'CreateTimestamp': createTimestamp,
'StartTimestamp': startTimestamp,
'EndTimestamp': endTimestamp,
'TaskName': taskName,
'Attempts': attempts,
'Bringup': bringup,
'TestFlaky': testFlaky,
'BuildNumber': buildNumber,
'Status': status,
};
}

@override
String toString() {
final StringBuffer buf = StringBuffer()
Expand Down
99 changes: 99 additions & 0 deletions app_dart/lib/src/request_handlers/get_status_firestore.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2019 The Flutter Authors. 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:gcloud/db.dart';
import 'package:github/github.dart';
import 'package:meta/meta.dart';

import '../model/appengine/commit.dart';
import '../model/appengine/key_helper.dart';
import '../request_handling/body.dart';
import '../request_handling/exceptions.dart';
import '../request_handling/request_handler.dart';
import '../service/build_status_provider.dart';
import '../service/config.dart';
import '../service/datastore.dart';
import '../service/firestore.dart';

@immutable
class GetStatusFirestore extends RequestHandler<Body> {
const GetStatusFirestore({
required super.config,
@visibleForTesting this.datastoreProvider = DatastoreService.defaultProvider,
@visibleForTesting this.buildStatusProvider = BuildStatusService.defaultProvider,
});

final DatastoreServiceProvider datastoreProvider;
final BuildStatusServiceProvider buildStatusProvider;

static const String kLastCommitKeyParam = 'lastCommitKey';
static const String kBranchParam = 'branch';
static const String kRepoParam = 'repo';

@override
Future<Body> get() async {
final String? encodedLastCommitKey = request!.uri.queryParameters[kLastCommitKeyParam];
final String repoName = request!.uri.queryParameters[kRepoParam] ?? Config.flutterSlug.name;
final RepositorySlug slug = RepositorySlug('flutter', repoName);
final String branch = request!.uri.queryParameters[kBranchParam] ?? Config.defaultBranch(slug);
final DatastoreService datastore = datastoreProvider(config.db);
final FirestoreService firestoreService = await config.createFirestoreService();
final BuildStatusService buildStatusService = buildStatusProvider(datastore, firestoreService);
final KeyHelper keyHelper = config.keyHelper;
final int commitNumber = config.commitNumber;
final int lastCommitTimestamp = await _obtainTimestamp(encodedLastCommitKey, keyHelper, datastore);

final List<SerializableCommitTasksStatus> statuses = await buildStatusService
.retrieveCommitStatusFirestore(
limit: commitNumber,
timestamp: lastCommitTimestamp,
branch: branch,
slug: slug,
)
.map<SerializableCommitTasksStatus>(
(CommitTasksStatus status) => SerializableCommitTasksStatus(status),
)
.toList();

return Body.forJson(<String, dynamic>{
'Statuses': statuses,
});
}

Future<int> _obtainTimestamp(String? encodedLastCommitKey, KeyHelper keyHelper, DatastoreService datastore) async {
/// [lastCommitTimestamp] is initially set as the current time, which allows query return
/// latest commit list. If [owerKeyParam] is not empty, [lastCommitTimestamp] will be set
/// as the timestamp of that [commit], and the query will return lastest commit
/// list whose timestamp is smaller than [lastCommitTimestamp].
int lastCommitTimestamp = DateTime.now().millisecondsSinceEpoch;

if (encodedLastCommitKey != null) {
final Key<String> ownerKey = keyHelper.decode(encodedLastCommitKey) as Key<String>;
final Commit commit = await datastore.db.lookupValue<Commit>(
ownerKey,
orElse: () => throw NotFoundException('Failed to find commit with key $ownerKey'),
);

lastCommitTimestamp = commit.timestamp!;
}
return lastCommitTimestamp;
}
}

/// The serialized representation of a [CommitStatus].
// TODO(tvolkert): Directly serialize [CommitStatus] once frontends migrate to new format.
class SerializableCommitTasksStatus {
const SerializableCommitTasksStatus(this.status);

final CommitTasksStatus status;

Map<String, dynamic> toJson() {
return <String, dynamic>{
'Commit': status.commit.facade,
'Tasks': status.tasks,
};
}
}
2 changes: 1 addition & 1 deletion app_dart/lib/src/service/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ class Config {
int get maxLuciTaskRetries => 2;

/// The default number of commit shown in flutter build dashboard.
int get commitNumber => 30;
int get commitNumber => 1;

KeyHelper get keyHelper => KeyHelper(applicationContext: context.applicationContext);

Expand Down
168 changes: 168 additions & 0 deletions dashboard/lib/model/commit_firestore.pb.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//
// Generated code. Do not modify.
// source: lib/model/commit_firestore.proto
//
// @dart = 2.12

// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

import 'dart:core' as $core;

import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;

class CommitDocument extends $pb.GeneratedMessage {
factory CommitDocument({
$core.String? documentName,
$fixnum.Int64? createTimestamp,
$core.String? sha,
$core.String? author,
$core.String? avatar,
$core.String? repositoryPath,
$core.String? branch,
$core.String? message,
}) {
final $result = create();
if (documentName != null) {
$result.documentName = documentName;
}
if (createTimestamp != null) {
$result.createTimestamp = createTimestamp;
}
if (sha != null) {
$result.sha = sha;
}
if (author != null) {
$result.author = author;
}
if (avatar != null) {
$result.avatar = avatar;
}
if (repositoryPath != null) {
$result.repositoryPath = repositoryPath;
}
if (branch != null) {
$result.branch = branch;
}
if (message != null) {
$result.message = message;
}
return $result;
}
CommitDocument._() : super();
factory CommitDocument.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory CommitDocument.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);

static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CommitDocument', package: const $pb.PackageName(_omitMessageNames ? '' : 'dashboard'), createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'documentName', protoName: 'documentName')
..aInt64(2, _omitFieldNames ? '' : 'createTimestamp', protoName: 'createTimestamp')
..aOS(3, _omitFieldNames ? '' : 'sha')
..aOS(4, _omitFieldNames ? '' : 'author')
..aOS(5, _omitFieldNames ? '' : 'avatar')
..aOS(6, _omitFieldNames ? '' : 'repositoryPath', protoName: 'repositoryPath')
..aOS(7, _omitFieldNames ? '' : 'branch')
..aOS(8, _omitFieldNames ? '' : 'message')
..hasRequiredFields = false
;

@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
CommitDocument clone() => CommitDocument()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
CommitDocument copyWith(void Function(CommitDocument) updates) => super.copyWith((message) => updates(message as CommitDocument)) as CommitDocument;

$pb.BuilderInfo get info_ => _i;

@$core.pragma('dart2js:noInline')
static CommitDocument create() => CommitDocument._();
CommitDocument createEmptyInstance() => create();
static $pb.PbList<CommitDocument> createRepeated() => $pb.PbList<CommitDocument>();
@$core.pragma('dart2js:noInline')
static CommitDocument getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CommitDocument>(create);
static CommitDocument? _defaultInstance;

/// Next ID: 9
@$pb.TagNumber(1)
$core.String get documentName => $_getSZ(0);
@$pb.TagNumber(1)
set documentName($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasDocumentName() => $_has(0);
@$pb.TagNumber(1)
void clearDocumentName() => clearField(1);

@$pb.TagNumber(2)
$fixnum.Int64 get createTimestamp => $_getI64(1);
@$pb.TagNumber(2)
set createTimestamp($fixnum.Int64 v) { $_setInt64(1, v); }
@$pb.TagNumber(2)
$core.bool hasCreateTimestamp() => $_has(1);
@$pb.TagNumber(2)
void clearCreateTimestamp() => clearField(2);

@$pb.TagNumber(3)
$core.String get sha => $_getSZ(2);
@$pb.TagNumber(3)
set sha($core.String v) { $_setString(2, v); }
@$pb.TagNumber(3)
$core.bool hasSha() => $_has(2);
@$pb.TagNumber(3)
void clearSha() => clearField(3);

@$pb.TagNumber(4)
$core.String get author => $_getSZ(3);
@$pb.TagNumber(4)
set author($core.String v) { $_setString(3, v); }
@$pb.TagNumber(4)
$core.bool hasAuthor() => $_has(3);
@$pb.TagNumber(4)
void clearAuthor() => clearField(4);

@$pb.TagNumber(5)
$core.String get avatar => $_getSZ(4);
@$pb.TagNumber(5)
set avatar($core.String v) { $_setString(4, v); }
@$pb.TagNumber(5)
$core.bool hasAvatar() => $_has(4);
@$pb.TagNumber(5)
void clearAvatar() => clearField(5);

@$pb.TagNumber(6)
$core.String get repositoryPath => $_getSZ(5);
@$pb.TagNumber(6)
set repositoryPath($core.String v) { $_setString(5, v); }
@$pb.TagNumber(6)
$core.bool hasRepositoryPath() => $_has(5);
@$pb.TagNumber(6)
void clearRepositoryPath() => clearField(6);

@$pb.TagNumber(7)
$core.String get branch => $_getSZ(6);
@$pb.TagNumber(7)
set branch($core.String v) { $_setString(6, v); }
@$pb.TagNumber(7)
$core.bool hasBranch() => $_has(6);
@$pb.TagNumber(7)
void clearBranch() => clearField(7);

@$pb.TagNumber(8)
$core.String get message => $_getSZ(7);
@$pb.TagNumber(8)
set message($core.String v) { $_setString(7, v); }
@$pb.TagNumber(8)
$core.bool hasMessage() => $_has(7);
@$pb.TagNumber(8)
void clearMessage() => clearField(8);
}


const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
11 changes: 11 additions & 0 deletions dashboard/lib/model/commit_firestore.pbenum.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: lib/model/commit_firestore.proto
//
// @dart = 2.12

// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

38 changes: 38 additions & 0 deletions dashboard/lib/model/commit_firestore.pbjson.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Generated code. Do not modify.
// source: lib/model/commit_firestore.proto
//
// @dart = 2.12

// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;

@$core.Deprecated('Use commitDocumentDescriptor instead')
const CommitDocument$json = {
'1': 'CommitDocument',
'2': [
{'1': 'documentName', '3': 1, '4': 1, '5': 9, '10': 'documentName'},
{'1': 'createTimestamp', '3': 2, '4': 1, '5': 3, '10': 'createTimestamp'},
{'1': 'sha', '3': 3, '4': 1, '5': 9, '10': 'sha'},
{'1': 'author', '3': 4, '4': 1, '5': 9, '10': 'author'},
{'1': 'avatar', '3': 5, '4': 1, '5': 9, '10': 'avatar'},
{'1': 'repositoryPath', '3': 6, '4': 1, '5': 9, '10': 'repositoryPath'},
{'1': 'branch', '3': 7, '4': 1, '5': 9, '10': 'branch'},
{'1': 'message', '3': 8, '4': 1, '5': 9, '10': 'message'},
],
};

/// Descriptor for `CommitDocument`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List commitDocumentDescriptor = $convert.base64Decode(
'Cg5Db21taXREb2N1bWVudBIiCgxkb2N1bWVudE5hbWUYASABKAlSDGRvY3VtZW50TmFtZRIoCg'
'9jcmVhdGVUaW1lc3RhbXAYAiABKANSD2NyZWF0ZVRpbWVzdGFtcBIQCgNzaGEYAyABKAlSA3No'
'YRIWCgZhdXRob3IYBCABKAlSBmF1dGhvchIWCgZhdmF0YXIYBSABKAlSBmF2YXRhchImCg5yZX'
'Bvc2l0b3J5UGF0aBgGIAEoCVIOcmVwb3NpdG9yeVBhdGgSFgoGYnJhbmNoGAcgASgJUgZicmFu'
'Y2gSGAoHbWVzc2FnZRgIIAEoCVIHbWVzc2FnZQ==');