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
35 changes: 35 additions & 0 deletions app_dart/lib/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'dart:math';
import 'cocoon_service.dart';
import 'src/request_handlers/get_engine_artifacts_ready.dart';
import 'src/request_handlers/get_presubmit_checks.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 @@ -156,8 +157,42 @@ 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/get-presubmit-checks': GetPresubmitChecks(
config: config,
authenticationProvider: authProvider,
firestore: firestore,
),
'/api/update-suppressed-test': UpdateSuppressedTest(
Expand Down
4 changes: 3 additions & 1 deletion app_dart/lib/src/request_handlers/get_presubmit_checks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'dart:io';
import 'package:cocoon_common/rpc_model.dart';

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

/// Returns all checks for a specific presubmit check run.
Expand All @@ -30,9 +31,10 @@ import '../service/firestore/unified_check_run.dart';
/// "summary": "Check passed"
/// }
/// ]
final class GetPresubmitChecks extends RequestHandler {
final class GetPresubmitChecks extends ApiRequestHandler {
const GetPresubmitChecks({
required super.config,
required super.authenticationProvider,
required FirestoreService firestore,
}) : _firestore = firestore;

Expand Down
96 changes: 96 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,96 @@
// 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 'dart:io';

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';

/// Request handler for retrieving the aggregated presubmit guard status.
///
/// This handler queries the presubmit guards for a specific commit SHA and
/// returns an aggregated response including the overall guard status and
/// individual stage statuses.
@immutable
final class GetPresubmitGuard extends ApiRequestHandler {
/// Defines the [GetPresubmitGuard] handler.
const GetPresubmitGuard({
required super.config,
required super.authenticationProvider,
required FirestoreService firestore,
}) : _firestore = firestore;

final FirestoreService _firestore;

/// The name of the query parameter for the repository slug (e.g. 'flutter/flutter').
static const String kSlugParam = 'slug';

/// The name of the query parameter for the commit SHA.
static const String kShaParam = 'sha';

/// Handles the HTTP GET request.
///
/// Requires [kSlugParam] and [kShaParam] query parameters.
/// Returns a JSON response with the aggregated presubmit guard data.
@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({
'error': 'No guard found for slug $slug and sha $sha',
}, statusCode: HttpStatus.notFound);
}

// 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: [
for (final g in guards)
rpc_model.PresubmitGuardStage(
name: g.stage.name,
createdAt: g.creationTime,
builds: g.builds,
),
],
);

return Response.json(response);
}
}
1 change: 1 addition & 0 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'package:cocoon_service/src/request_handlers/get_presubmit_checks.dart';
import 'package:test/test.dart';

import '../src/fake_config.dart';
import '../src/request_handling/fake_dashboard_authentication.dart';
import '../src/request_handling/fake_http.dart';
import '../src/request_handling/request_handler_tester.dart';
import '../src/service/fake_firestore_service.dart';
Expand All @@ -30,7 +31,11 @@ void main() {
config = FakeConfig();
tester = RequestHandlerTester();
firestoreService = FakeFirestoreService();
handler = GetPresubmitChecks(config: config, firestore: firestoreService);
handler = GetPresubmitChecks(
config: config,
firestore: firestoreService,
authenticationProvider: FakeDashboardAuthentication(),
);
});

Future<List<PresubmitCheckResponse>?> getPresubmitCheckResponse(
Expand Down
Loading
Loading