blob: 7c0de91300c0858763af91af194829686696851b [file] [log] [blame]
// 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:github/github.dart';
import 'package:meta/meta.dart';
import '../../cocoon_service.dart';
import '../model/appengine/github_build_status_update.dart';
import '../request_handling/api_request_handler.dart';
import '../service/build_status_provider.dart';
import '../service/datastore.dart';
import '../service/logging.dart';
@immutable
class PushBuildStatusToGithub extends ApiRequestHandler<Body> {
const PushBuildStatusToGithub({
required super.config,
required super.authenticationProvider,
@visibleForTesting DatastoreServiceProvider? datastoreProvider,
@visibleForTesting BuildStatusServiceProvider? buildStatusServiceProvider,
}) : datastoreProvider = datastoreProvider ?? DatastoreService.defaultProvider,
buildStatusServiceProvider = buildStatusServiceProvider ?? BuildStatusService.defaultProvider;
final BuildStatusServiceProvider buildStatusServiceProvider;
final DatastoreServiceProvider datastoreProvider;
static const String fullNameRepoParam = 'repo';
@override
Future<Body> get() async {
if (authContext!.clientContext.isDevelopmentEnvironment) {
// Don't push GitHub status from the local dev server.
log.fine('GitHub statuses are not pushed from local dev environments');
return Body.empty;
}
final String repository = request!.uri.queryParameters[fullNameRepoParam] ?? 'flutter/flutter';
final RepositorySlug slug = RepositorySlug.full(repository);
final DatastoreService datastore = datastoreProvider(config.db);
final BuildStatusService buildStatusService = buildStatusServiceProvider(datastore);
final BuildStatus status = (await buildStatusService.calculateCumulativeStatus(slug))!;
await _insertBigquery(slug, status.githubStatus, Config.defaultBranch(slug), config);
await _updatePRs(slug, status.githubStatus, datastore);
log.fine('All the PRs for $repository have been updated with $status');
return Body.empty;
}
Future<void> _updatePRs(RepositorySlug slug, String status, DatastoreService datastore) async {
final GitHub github = await config.createGitHubClient(slug: slug);
final List<GithubBuildStatusUpdate> updates = <GithubBuildStatusUpdate>[];
await for (PullRequest pr in github.pullRequests.list(slug)) {
// Tree status is only put on PRs merging into ToT.
if (pr.base!.ref != Config.defaultBranch(slug)) {
log.fine('This PR is not staged to land on ${Config.defaultBranch(slug)}, skipping.');
continue;
}
final GithubBuildStatusUpdate update = await datastore.queryLastStatusUpdate(slug, pr);
if (update.status != status) {
log.fine('Updating status of ${slug.fullName}#${pr.number} from ${update.status} to $status');
final CreateStatus request = CreateStatus(status);
request.targetUrl = 'https://flutter-dashboard.appspot.com/#/build?repo=${slug.name}';
request.context = 'tree-status';
if (status != GithubBuildStatusUpdate.statusSuccess) {
request.description = config.flutterBuildDescription;
}
try {
await github.repositories.createStatus(slug, pr.head!.sha!, request);
update.status = status;
update.updates = (update.updates ?? 0) + 1;
update.updateTimeMillis = DateTime.now().millisecondsSinceEpoch;
updates.add(update);
} catch (error) {
log.severe('Failed to post status update to ${slug.fullName}#${pr.number}: $error');
}
}
}
await datastore.insert(updates);
}
Future<void> _insertBigquery(RepositorySlug slug, String status, String branch, Config config) async {
const String bigqueryTableName = 'BuildStatus';
final Map<String, dynamic> bigqueryData = <String, dynamic>{
'Timestamp': DateTime.now().millisecondsSinceEpoch,
'Status': status,
'Branch': branch,
'Repo': slug.name,
};
await insertBigquery(bigqueryTableName, bigqueryData, await config.createTabledataResourceApi());
}
}