in app_dart/lib/src/request_handlers/push_gold_status_to_github.dart [55:197]
Future<void> _sendStatusUpdates(
DatastoreService datastore,
RepositorySlug slug,
) async {
final GitHub gitHubClient = await config.createGitHubClient(slug: slug);
final List<GithubGoldStatusUpdate> statusUpdates = <GithubGoldStatusUpdate>[];
log.fine('Beginning Gold checks...');
await for (PullRequest pr in gitHubClient.pullRequests.list(slug)) {
assert(pr.number != null);
// Get last known Gold status from datastore.
final GithubGoldStatusUpdate lastUpdate = await datastore.queryLastGoldUpdate(slug, pr);
CreateStatus statusRequest;
log.fine('Last known Gold status for $slug#${pr.number} was with sha: '
'${lastUpdate.head}, status: ${lastUpdate.status}, description: ${lastUpdate.description}');
if (lastUpdate.status == GithubGoldStatusUpdate.statusCompleted && lastUpdate.head == pr.head!.sha) {
log.fine('Completed status already reported for this commit.');
// We have already seen this commit and it is completed or, this is not
// a change staged to land on master, which we should ignore.
continue;
}
final String defaultBranch = Config.defaultBranch(slug);
if (pr.base!.ref != defaultBranch) {
log.fine('This change is not staged to land on $defaultBranch, skipping.');
// This is potentially a release branch, or another change not landing
// on master, we don't need a Gold check.
continue;
}
if (pr.draft!) {
log.fine('This pull request is a draft.');
// We don't want to query Gold while a PR is in a draft state, and we
// don't want to needlessly hold a pending state either.
// If a PR has been marked `draft` after the fact, and there has not
// been a new commit, we cannot rescind a previously posted status, so
// if it is already pending, we should make the contributor aware of
// that fact.
if (lastUpdate.status == GithubGoldStatusUpdate.statusRunning &&
lastUpdate.head == pr.head!.sha &&
!await _alreadyCommented(gitHubClient, pr, slug, config.flutterGoldDraftChange)) {
await gitHubClient.issues
.createComment(slug, pr.number!, config.flutterGoldDraftChange + config.flutterGoldAlertConstant(slug));
}
continue;
}
log.fine('Querying builds for pull request #${pr.number} with sha: ${lastUpdate.head}...');
final GraphQLClient gitHubGraphQLClient = await config.createGitHubGraphQLClient();
final List<String> incompleteChecks = <String>[];
bool runsGoldenFileTests = false;
final Map<String, dynamic> data = (await _queryGraphQL(
gitHubGraphQLClient,
slug,
pr.number!,
))!;
final Map<String, dynamic> prData = data['repository']['pullRequest'] as Map<String, dynamic>;
final Map<String, dynamic> commit = prData['commits']['nodes'].single['commit'] as Map<String, dynamic>;
List<Map<String, dynamic>>? checkRuns;
if (commit['checkSuites']['nodes'] != null && (commit['checkSuites']['nodes'] as List<dynamic>).isNotEmpty) {
checkRuns =
(commit['checkSuites']['nodes']?.first['checkRuns']['nodes'] as List<dynamic>).cast<Map<String, dynamic>>();
}
checkRuns = checkRuns ?? <Map<String, dynamic>>[];
log.fine('This PR has ${checkRuns.length} checks.');
for (Map<String, dynamic> checkRun in checkRuns) {
log.fine('Check run: $checkRun');
final String name = checkRun['name'].toLowerCase() as String;
if (name.contains('framework') || name.contains('web engine')) {
runsGoldenFileTests = true;
}
if (checkRun['conclusion'] == null || checkRun['conclusion'].toUpperCase() != 'SUCCESS') {
incompleteChecks.add(name);
}
}
if (runsGoldenFileTests) {
log.fine('This PR executes golden file tests.');
// Check when this PR was last updated. Gold does not keep results after
// >20 days. If a PR has gone stale, we should draw attention to it to be
// updated or closed.
final DateTime updatedAt = pr.updatedAt!.toUtc();
final DateTime twentyDaysAgo = DateTime.now().toUtc().subtract(const Duration(days: 20));
if (updatedAt.isBefore(twentyDaysAgo)) {
log.fine('Stale PR, no gold status to report.');
if (!await _alreadyCommented(gitHubClient, pr, slug, config.flutterGoldStalePR)) {
log.fine('Notifying for stale PR.');
await gitHubClient.issues
.createComment(slug, pr.number!, config.flutterGoldStalePR + config.flutterGoldAlertConstant(slug));
}
continue;
}
if (incompleteChecks.isNotEmpty) {
// If checks on an open PR are running or failing, the gold status
// should just be pending. Any draft PRs are skipped
// until marked ready for review.
log.fine('Waiting for checks to be completed.');
statusRequest =
_createStatus(GithubGoldStatusUpdate.statusRunning, config.flutterGoldPending, slug, pr.number!);
} else {
// We do not want to query Gold on a draft PR.
assert(!pr.draft!);
// Get Gold status.
final String goldStatus = await _getGoldStatus(slug, pr);
statusRequest = _createStatus(
goldStatus,
goldStatus == GithubGoldStatusUpdate.statusRunning
? config.flutterGoldChanges
: config.flutterGoldSuccess,
slug,
pr.number!);
log.fine('New status for potential update: ${statusRequest.state}, ${statusRequest.description}');
if (goldStatus == GithubGoldStatusUpdate.statusRunning &&
!await _alreadyCommented(gitHubClient, pr, slug, config.flutterGoldCommentID(pr))) {
log.fine('Notifying for triage.');
await _commentAndApplyGoldLabels(gitHubClient, pr, slug);
}
}
// Push updates if there is a status change (detected by unique description)
// or this is a new commit.
if (lastUpdate.description != statusRequest.description || lastUpdate.head != pr.head!.sha) {
try {
log.fine('Pushing status to GitHub: ${statusRequest.state}, ${statusRequest.description}');
await gitHubClient.repositories.createStatus(slug, pr.head!.sha!, statusRequest);
lastUpdate.status = statusRequest.state!;
lastUpdate.head = pr.head!.sha;
lastUpdate.updates = (lastUpdate.updates ?? 0) + 1;
lastUpdate.description = statusRequest.description!;
statusUpdates.add(lastUpdate);
} catch (error) {
log.severe('Failed to post status update to ${slug.fullName}#${pr.number}: $error');
}
}
} else {
log.fine('This PR does not execute golden file tests.');
}
}
await datastore.insert(statusUpdates);
log.fine('Committed all updates for $slug');
}