Skip to content
34 changes: 34 additions & 0 deletions app_dart/lib/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:math';

import 'cocoon_service.dart';
import 'src/request_handlers/get_engine_artifacts_ready.dart';
import 'src/request_handlers/get_presubmit_guard.dart';
import 'src/request_handlers/get_tree_status_changes.dart';
import 'src/request_handlers/github_webhook_replay.dart';
import 'src/request_handlers/lookup_hash.dart';
Expand Down Expand Up @@ -155,6 +156,39 @@ Server createServer({
authenticationProvider: authProvider,
firestore: firestore,
),

/// Returns the presubmit guard status for a given slug and commit SHA.
///
/// Consolidates multiple [PresubmitGuard] records (one per stage) into a single response.
///
/// GET: /api/get-presubmit-guard
///
/// Parameters:
/// slug: (string in query) required. The repository owner/name (e.g., 'flutter/flutter').
/// sha: (string in query) required. The commit SHA to query for.
///
/// Response: Status 200 OK
/// Returns [PresubmitGuardResponse]:
/// {
/// "pr_num": 123,
/// "check_run_id": 456,
/// "author": "dash",
/// "stages": [
/// {
/// "name": "fusion",
/// "created_at": 123456789,
/// "builds": {
/// "test1": "Succeeded",
/// "test2": "In Progress"
/// }
/// }
/// ]
/// }
'/api/get-presubmit-guard': GetPresubmitGuard(
config: config,
authenticationProvider: authProvider,
firestore: firestore,
),
'/api/update-suppressed-test': UpdateSuppressedTest(
authenticationProvider: authProvider,
firestore: firestore,
Expand Down
5 changes: 1 addition & 4 deletions app_dart/lib/src/model/ci_yaml/target.dart
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,6 @@ class Dependency {
final String? version;

Map<String, Object> toJson() {
return <String, Object>{
'dependency': name,
if (version != null) 'version': version!,
};
return <String, Object>{'dependency': name, 'version': ?version};
}
}
6 changes: 3 additions & 3 deletions app_dart/lib/src/model/firestore/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ abstract class AppDocument<T extends AppDocument<T>> implements g.Document {
// Copied from [g.Document.toJson].
return {
'fields': fields,
if (createTime != null) 'createTime': createTime!,
if (name != null) 'name': name!,
if (updateTime != null) 'updateTime': updateTime!,
'createTime': ?createTime,
'name': ?name,
'updateTime': ?updateTime,
};
}

Expand Down
81 changes: 81 additions & 0 deletions app_dart/lib/src/request_handlers/get_presubmit_guard.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2026 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:cocoon_common/guard_status.dart';
import 'package:cocoon_common/rpc_model.dart' as rpc_model;
import 'package:github/github.dart';
import 'package:meta/meta.dart';

import '../../cocoon_service.dart';
import '../request_handling/api_request_handler.dart';
import '../service/firestore/unified_check_run.dart';

@immutable
final class GetPresubmitGuard extends ApiRequestHandler {
const GetPresubmitGuard({
required super.config,
required super.authenticationProvider,
required FirestoreService firestore,
}) : _firestore = firestore;

final FirestoreService _firestore;

static const String kSlugParam = 'slug';
static const String kShaParam = 'sha';

@override
Future<Response> get(Request request) async {
checkRequiredQueryParameters(request, [kSlugParam, kShaParam]);

final slugName = request.uri.queryParameters[kSlugParam]!;
final sha = request.uri.queryParameters[kShaParam]!;

final slug = RepositorySlug.full(slugName);
final guards = await UnifiedCheckRun.getPresubmitGuardsForCommitSha(
firestoreService: _firestore,
slug: slug,
commitSha: sha,
);

if (guards.isEmpty) {
return Response.json(null);
}

// Consolidate metadata from the first record.
final first = guards.first;

final GuardStatus guardStatus;
if (guards.any((g) => g.failedBuilds > 0)) {
guardStatus = GuardStatus.failed;
} else if (guards.every(
(g) => g.failedBuilds == 0 && g.remainingBuilds == 0,
)) {
guardStatus = GuardStatus.succeeded;
} else if (guards.every((g) => g.remainingBuilds == g.builds.length)) {
guardStatus = GuardStatus.waitingForBackfill;
} else {
guardStatus = GuardStatus.inProgress;
}

final response = rpc_model.PresubmitGuardResponse(
prNum: first.pullRequestId,
checkRunId: first.checkRunId,
author: first.author,
guardStatus: guardStatus,
stages: [
...guards.map(
(g) => rpc_model.PresubmitGuardStage(
name: g.stage.name,
createdAt: g.creationTime,
builds: g.builds,
),
),
],
);

return Response.json(response.toJson());
}
}
7 changes: 4 additions & 3 deletions app_dart/lib/src/service/firestore.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import '../../cocoon_service.dart';
import '../model/firestore/commit.dart';
import '../model/firestore/github_build_status.dart';
import '../model/firestore/github_gold_status.dart';

import '../model/firestore/task.dart';
import 'firestore/commit_and_tasks.dart';

Expand Down Expand Up @@ -101,7 +102,7 @@ mixin FirestoreQueries {
created ??= TimeRange.indefinite;
final filterMap = <String, Object>{
'${Commit.fieldRepositoryPath} =': slug.fullName,
if (branch != null) '${Commit.fieldBranch} =': branch,
'${Commit.fieldBranch} =': ?branch,
..._filterByTimeRange(Commit.fieldCreateTimestamp, created),
};
final orderMap = <String, String>{
Expand All @@ -124,9 +125,9 @@ mixin FirestoreQueries {
Transaction? transaction,
}) async {
final filterMap = {
if (name != null) '${Task.fieldName} =': name,
'${Task.fieldName} =': ?name,
if (status != null) '${Task.fieldStatus} =': status.value,
if (commitSha != null) '${Task.fieldCommitSha} =': commitSha,
'${Task.fieldCommitSha} =': ?commitSha,
};

// Avoid a full table-scan.
Expand Down
17 changes: 15 additions & 2 deletions app_dart/lib/src/service/firestore/unified_check_run.dart
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,19 @@ final class UnifiedCheckRun {
)).firstOrNull;
}

/// Queries for [PresubmitGuard] records by [slug] and [commitSha].
static Future<List<PresubmitGuard>> getPresubmitGuardsForCommitSha({
required FirestoreService firestoreService,
required RepositorySlug slug,
required String commitSha,
}) async {
return await _queryPresubmitGuards(
firestoreService: firestoreService,
slug: slug,
commitSha: commitSha,
);
}

static Future<List<PresubmitGuard>> _queryPresubmitGuards({
required FirestoreService firestoreService,
Transaction? transaction,
Expand All @@ -246,10 +259,10 @@ final class UnifiedCheckRun {
int? limit,
}) async {
final filterMap = {
'${PresubmitGuard.fieldSlug} =': ?slug,
'${PresubmitGuard.fieldSlug} =': ?slug?.fullName,
'${PresubmitGuard.fieldPullRequestId} =': ?pullRequestId,
'${PresubmitGuard.fieldCheckRunId} =': ?checkRunId,
'${PresubmitGuard.fieldStage} =': ?stage,
'${PresubmitGuard.fieldStage} =': ?stage?.name,
'${PresubmitGuard.fieldCreationTime} =': ?creationTime,
'${PresubmitGuard.fieldAuthor} =': ?author,
'${PresubmitGuard.fieldCommitSha} =': ?commitSha,
Expand Down
8 changes: 2 additions & 6 deletions app_dart/lib/src/service/luci_build_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -668,9 +668,7 @@ class LuciBuildService {
log.info(
'create postsubmit schedule request for target: ${pending.target} in commit ${commit.sha}',
);
final properties = <String, Object>{
if (contentHash != null) 'content_hash': contentHash,
};
final properties = <String, Object>{'content_hash': ?contentHash};
final scheduleBuildRequest = await _createPostsubmitScheduleBuild(
commit: commit,
target: pending.target,
Expand Down Expand Up @@ -736,9 +734,7 @@ class LuciBuildService {
'create postsubmit schedule request for target: $target in commit ${commit.sha}',
);

final properties = <String, Object>{
if (contentHash != null) 'content_hash': contentHash,
};
final properties = <String, Object>{'content_hash': ?contentHash};
final scheduleBuildRequest = await _createMergeGroupScheduleBuild(
commit: commit,
target: target,
Expand Down
5 changes: 1 addition & 4 deletions app_dart/lib/src/service/scheduler/ci_yaml_fetcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,7 @@ interface class CiYamlFetcher {
// If totCiYaml is not null, we assume the caller has verified that the
// current branch is not a release branch.
return CiYamlSet(
yamls: {
CiType.any: rootConfig,
if (engineConfig != null) CiType.fusionEngine: engineConfig,
},
yamls: {CiType.any: rootConfig, CiType.fusionEngine: ?engineConfig},
slug: commit.slug,
branch: commit.branch,
totConfig: totCiYaml,
Expand Down
Loading
Loading