Skip to content
Merged
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
31 changes: 18 additions & 13 deletions app_dart/lib/src/foundation/github_checks_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,37 @@
// found in the LICENSE file.

import 'dart:core';
import 'dart:io';

import 'package:cocoon_server/logging.dart';
import 'package:github/github.dart' as github;
import 'package:github/hooks.dart';
import 'package:retry/retry.dart';

import '../request_handling/http_utils.dart';
import '../service/config.dart';

/// Wrapper class for github checkrun service. This is used to simplify
/// mocking during testing because some of the subclasses are private.
class GithubChecksUtil {
const GithubChecksUtil();
Future<Map<String, github.CheckRun>> allCheckRuns(
github.GitHub gitHubClient,
CheckSuiteEvent checkSuiteEvent,
Config config,
github.RepositorySlug slug,
int checkSuiteId,
) async {
final allCheckRuns = await gitHubClient.checks.checkRuns
.listCheckRunsInSuite(
checkSuiteEvent.repository!.slug(),
checkSuiteId: checkSuiteEvent.checkSuite!.id!,
)
.toList();
return {
for (github.CheckRun check in allCheckRuns) check.name as String: check,
};
final gitHubClient = await config.createGitHubClient(slug: slug);
const r = RetryOptions(maxAttempts: 3, delayFactor: Duration(seconds: 2));
return r.retry(
() async {
final allCheckRuns = await gitHubClient.checks.checkRuns
.listCheckRunsInSuite(slug, checkSuiteId: checkSuiteId)
.toList();
return {
for (github.CheckRun check in allCheckRuns)
check.name as String: check,
};
},
retryIf: (Exception e) => e is github.GitHubError || e is SocketException,
Comment thread
ievdokdm marked this conversation as resolved.
);
}

Future<github.CheckSuite> getCheckSuite(
Expand Down
3 changes: 3 additions & 0 deletions app_dart/lib/src/model/common/presubmit_completed_check.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class PresubmitCompletedJob {
final String? summary;
final int? buildNumber;
final Int64? buildId;
final String? author;

const PresubmitCompletedJob({
required this.name,
Expand All @@ -60,6 +61,7 @@ class PresubmitCompletedJob {
this.summary,
this.buildNumber,
this.buildId,
this.author,
});

/// Creates a [PresubmitCompletedJob] from a BuildBucket [Build].
Expand Down Expand Up @@ -89,6 +91,7 @@ class PresubmitCompletedJob {
].join('\n---\n'),
buildNumber: build.number,
buildId: build.id,
author: BuildTags.fromStringPairs(build.tags).author,
);
}

Expand Down
4 changes: 2 additions & 2 deletions app_dart/lib/src/model/common/presubmit_guard_conclusion.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ class PresubmitGuardConclusion {

bool get isPending => isOk && remaining > 0;

bool get isFailed => isOk && !isPending && failed > 0;
bool get isFailed => isOk && failed > 0;

bool get isComplete => isOk && !isPending && !isFailed;
bool get isSucceeded => isOk && !isPending && !isFailed;

@override
bool operator ==(Object other) =>
Expand Down
6 changes: 3 additions & 3 deletions app_dart/lib/src/request_handlers/presubmit_subscription.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import '../service/scheduler/ci_yaml_fetcher.dart';
/// * Checking remaining build attempts and rescheduling failed builds.
/// * Suppressing failing conclusions if a test is marked as suppressed.
/// * Updating GitHub Check Run statuses for individual presubmit builds.
/// * Calling [Scheduler.processCheckRunCompleted] to progress CI stages or merge queues.
/// * Calling [Scheduler.processBuildCompleted] to progress CI stages or merge queues.
base class PresubmitSubscription extends SubscriptionHandler {
/// Creates an endpoint for listening to LUCI status updates.
const PresubmitSubscription({
Expand Down Expand Up @@ -134,7 +134,7 @@ base class PresubmitSubscription extends SubscriptionHandler {
///
/// Evaluates whether a failing task should be automatically retried up to
/// [_getMaxAttempt]. If the build is not rescheduled, updates GitHub check
/// run status and notifies [Scheduler.processCheckRunCompleted].
/// run status and notifies [Scheduler.processBuildCompleted].
Future<void> _processBuild({
required bbv2.Build build,
required PresubmitUserData userData,
Expand Down Expand Up @@ -225,7 +225,7 @@ base class PresubmitSubscription extends SubscriptionHandler {
: null,
summaryPrepend: suppressedMessage,
);
await _scheduler.processCheckRunCompleted(check);
await _scheduler.processBuildCompleted(check);
}
}

Expand Down
4 changes: 4 additions & 0 deletions app_dart/lib/src/service/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ interface class Config extends DynamicallyUpdatedConfig {
/// for users opted into the unified checkrun flow.
static const String kDashboardCheckName = 'Dashboard Checks';

/// A required check that fails if at least one job is failed and reset to
/// in-progress when all the failed jobs are retried.
static const String kPresubmitCheckName = 'Presubmit';

final CacheService _cache;
final SecretManager _secrets;
final http.Client _httpClient;
Expand Down
10 changes: 5 additions & 5 deletions app_dart/lib/src/service/github_checks_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import '../model/bbv2_extension.dart';
import 'config.dart';
import 'luci_build_service.dart';

const String kGithubSummary = '''
const String kCheckRunHeader = '''
**[Understanding a LUCI build failure](https://github.com/flutter/flutter/blob/master/docs/infra/Understanding-a-LUCI-build-failure.md)**

''';
Expand Down Expand Up @@ -98,7 +98,7 @@ class GithubChecksService {
allFields: true,
),
);
var summary = getGithubSummary(buildbucketBuild.summaryMarkdown);
var summary = getSummary(buildbucketBuild.summaryMarkdown);
if (summaryPrepend != null && summaryPrepend.isNotEmpty) {
summary = '$summaryPrepend\n\n$summary';
}
Expand All @@ -122,11 +122,11 @@ class GithubChecksService {

/// Appends triage wiki page to `summaryMarkdown` from LUCI build so that people can easily
/// reference from github check run page.
String getGithubSummary(String? summary) {
return getGithubSummaryWithHeader(kGithubSummary, summary);
String getSummary(String? summary) {
return getSummaryWithHeader(kCheckRunHeader, summary);
}

String getGithubSummaryWithHeader(String header, String? summary) {
String getSummaryWithHeader(String header, String? summary) {
if (summary == null) {
return '${header}Empty summaryMarkdown';
}
Expand Down
80 changes: 60 additions & 20 deletions app_dart/lib/src/service/luci_build_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ class LuciBuildService {

final checkRuns = <CheckRun>[];
late PresubmitUserData userData;
// If the unified check run flow is enabled, do not create individual
// check runs for each target but use the guard check run instead.
// In presubmit do not create individual check runs for each target but use
// the guard check run instead.
if (dashboardChecks != null) {
userData = PresubmitUserData(
commit: CommitRef(slug: slug, sha: commitSha, branch: commitBranch),
Expand All @@ -322,8 +322,7 @@ class LuciBuildService {
}

for (final MapEntry(key: target, value: attemptNumber) in targets.entries) {
// If the unified check run flow is disabled create individual check runs
// for each target.
// In merge queue create individual check runs for each target.
if (dashboardChecks == null) {
final checkRun = await _githubChecksUtil.createCheckRun(
_config,
Expand Down Expand Up @@ -393,22 +392,19 @@ class LuciBuildService {
cipdVersion: cipdVersion,
userData: userData,
properties: properties,
// if unified check run flow is enabled, use guard check run othervise check run id.
tags: dashboardChecks != null
? BuildTags([
GuardCheckRunIdBuildTag(
guardCheckRunId: dashboardChecks.id!,
),
if (attemptNumber > 1)
CurrentAttemptBuildTag(attemptNumber: attemptNumber),
if (isOrderedPresubmit)
OrderingKeyTag(orderingKey: pullRequest.head!.sha!),
])
: BuildTags([
GitHubCheckRunIdBuildTag(checkRunId: userData.checkRunId!),
if (isOrderedPresubmit)
OrderingKeyTag(orderingKey: pullRequest.head!.sha!),
]),
// In merge queue use check run id otherwise guard check run id.
tags: BuildTags([
if (pullRequest.user?.login != null)
AuthorBuildTag(value: pullRequest.user!.login!),
if (dashboardChecks != null)
GuardCheckRunIdBuildTag(guardCheckRunId: dashboardChecks.id!),
if (dashboardChecks == null && userData.checkRunId != null)
GitHubCheckRunIdBuildTag(checkRunId: userData.checkRunId!),
if (attemptNumber > 1)
CurrentAttemptBuildTag(attemptNumber: attemptNumber),
if (isOrderedPresubmit)
OrderingKeyTag(orderingKey: pullRequest.head!.sha!),
]),
dimensions: requestedDimensions,
),
),
Expand Down Expand Up @@ -443,6 +439,50 @@ class LuciBuildService {
);
}

// Set the presubmit check run status to `CheckRunStatus.inProgress` if
// Re-run all Failed Jobs.
if (pullRequest.user?.login != null &&
_config.flags.isResetFailedCheckRunEnabledForUser(
pullRequest.user!.login!,
)) {
final isRerun = targets.values.first > 1;
if (isRerun && stage != null && dashboardChecks != null) {
try {
final presubmitGuardDoc = await _firestore.getDocument(
PresubmitGuard.documentNameFor(
slug: slug,
prNum: pullRequest.number!,
checkRunId: dashboardChecks.id!,
stage: stage,
),
);
final guard = PresubmitGuard.fromDocument(presubmitGuardDoc);
final checkRun = guard.checkRun;

if (guard.failedJobs == 0) {
log.info('Re-creating Presubmit check run for Guard $guard');
await _githubChecksUtil.createCheckRun(
_config,
slug,
checkRun.headSha ?? commitSha,
Config.kPresubmitCheckName,
output: const CheckRunOutput(
title: Config.kPresubmitCheckName,
summary: Scheduler.kPresubmitCheckDescription,
),
detailsUrl: checkRun.detailsUrl,
);
}
} catch (e, s) {
// We are not going to block on this error.
log.warn(
'Failed to re-create Presubmit check run for PR# ${pullRequest.number}',
e,
s,
);
}
}
}
return targets.keys.toList();
}

Expand Down
18 changes: 18 additions & 0 deletions app_dart/lib/src/service/luci_build_service/build_tags.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ final class BuildTags {
final prTag = getTagOfType<GitHubPullRequestBuildTag>();
return prTag!.pullRequestNumber;
}

/// GitHub Pull Request Author
String? get author {
final tag = getTagOfType<AuthorBuildTag>();
return tag?.value;
}
}

/// Valid tags for [bbv2.ScheduleBuildRequest.tags].
Expand Down Expand Up @@ -170,6 +176,8 @@ sealed class BuildTag {
return TriggerdByBuildTag(email: pair.value);
case OrderingKeyTag._keyName:
return OrderingKeyTag(orderingKey: pair.value);
case AuthorBuildTag._keyName:
return AuthorBuildTag(value: pair.value);
}
return UnknownBuildTag(key: pair.key, value: pair.value);
}
Expand Down Expand Up @@ -243,6 +251,16 @@ final class UserAgentBuildTag extends BuildTag {
final String value;
}

/// The author of the commit that triggered the build.
final class AuthorBuildTag extends BuildTag {
static const _keyName = 'author';

AuthorBuildTag({required this.value}) : super(_keyName, value);

/// Name of the author.
final String value;
}

/// Groups builds together, i.e. by a (Gerrit) CL, (GitHub) PR or (Git) commit.
sealed class BuildSetBuildTag extends BuildTag {
static const _keyName = 'buildset';
Expand Down
Loading
Loading