Skip to content
Open
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
1 change: 1 addition & 0 deletions app_dart/lib/cocoon_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export 'src/request_handlers/github/webhook_subscription.dart';
export 'src/request_handlers/github_rate_limit_status.dart';
export 'src/request_handlers/github_webhook.dart';
export 'src/request_handlers/postsubmit_luci_subscription.dart';
export 'src/request_handlers/presubmit_guard_update_subscription.dart';
export 'src/request_handlers/presubmit_luci_subscription.dart';
export 'src/request_handlers/push_build_status_to_github.dart';
export 'src/request_handlers/push_gold_status_to_github.dart';
Expand Down
6 changes: 6 additions & 0 deletions app_dart/lib/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ Server createServer({
ciYamlFetcher: ciYamlFetcher,
firestore: firestore,
),
'/api/v2/presubmit-guard-update-subscription':
PresubmitGuardUpdateSubscription(
cache: cache,
config: config,
scheduler: scheduler,
),
'/api/v2/postsubmit-luci-subscription': PostsubmitLuciSubscription(
cache: cache,
config: config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ final class GetEngineArtifactsReady extends PublicApiRequestHandler {
final engineGuard = guards
.where((g) => g.stage == CiStage.fusionEngineBuild)
.firstOrNull;
remaining = engineGuard?.remainingJobs ?? 0;
failed = engineGuard?.failedJobs ?? 0;
if (engineGuard != null) {
failed = engineGuard.failedJobs;
remaining = engineGuard.remainingJobs;
}
}
}

Expand Down
33 changes: 19 additions & 14 deletions app_dart/lib/src/request_handlers/get_presubmit_guard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,24 @@ final class GetPresubmitGuard extends PublicApiRequestHandler {
// Consolidate metadata from the first record.
final first = guards.first;

final totalFailed = guards.fold<int>(0, (sum, g) => sum + g.failedJobs);
final totalRemaining = guards.fold<int>(
0,
(sum, g) => sum + g.remainingJobs,
);
final totalBuilds = guards.fold<int>(0, (sum, g) => sum + g.jobs.length);
var totalFailed = 0;
var totalRemaining = 0;
var totalBuilds = 0;
final stages = <rpc_model.PresubmitGuardStage>[];

for (final g in guards) {
totalFailed += g.failedJobs;
totalRemaining += g.remainingJobs;
totalBuilds += g.jobs.length;

stages.add(
rpc_model.PresubmitGuardStage(
name: g.stage.name,
createdAt: g.creationTime,
jobs: g.jobs,
),
);
Comment thread
eyebrowsoffire marked this conversation as resolved.
}

final guardStatus = GuardStatus.calculate(
failedBuilds: totalFailed,
Expand All @@ -123,14 +135,7 @@ final class GetPresubmitGuard extends PublicApiRequestHandler {
author: first.author,
guardStatus: guardStatus,
enableGeminiLogAnalysis: config.flags.enableGeminiLogAnalysis,
stages: [
for (final g in guards)
rpc_model.PresubmitGuardStage(
name: g.stage.name,
createdAt: g.creationTime,
jobs: g.jobs,
),
],
stages: stages,
);

return Response.json(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,16 @@ final class GetPresubmitGuardSummaries extends PublicApiRequestHandler {
final sha = entry.key;
final shaGuards = entry.value;

final totalFailed = shaGuards.fold<int>(
0,
(int sum, PresubmitGuard g) => sum + g.failedJobs,
);
final totalRemaining = shaGuards.fold<int>(
0,
(int sum, PresubmitGuard g) => sum + g.remainingJobs,
);
final totalBuilds = shaGuards.fold<int>(
0,
(int sum, PresubmitGuard g) => sum + g.jobs.length,
);
var totalFailed = 0;
var totalRemaining = 0;
var totalBuilds = 0;

for (final g in shaGuards) {
totalFailed += g.failedJobs;
totalRemaining += g.remainingJobs;
totalBuilds += g.jobs.length;
}

final earliestCreationTime = shaGuards.fold<int>(
// assuming creation time is always in the past :)
DateTime.now().millisecondsSinceEpoch,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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:convert';

import 'package:cocoon_server/logging.dart';
import 'package:meta/meta.dart';

import '../../cocoon_service.dart';
import '../request_handling/subscription_handler.dart';

/// An endpoint for listening to PubSub updates to debounce and synchronize
/// [PresubmitGuard] state in Firestore and process stage completions.
@immutable
final class PresubmitGuardUpdateSubscription extends SubscriptionHandler {
/// Creates an endpoint for processing debounced [PresubmitGuard] updates.
const PresubmitGuardUpdateSubscription({
required super.cache,
required super.config,
required Scheduler scheduler,
super.authProvider,
}) : _scheduler = scheduler,
super(subscriptionName: 'presubmit-guard-update-sub');

final Scheduler _scheduler;

@override
Future<Response> post(Request request) async {
if (message.data == null || message.data!.isEmpty) {
log.info('presubmit-guard-update: No data in message.');
return Response.emptyOk;
}

late final Map<String, dynamic> messageJson;
try {
messageJson = jsonDecode(message.data!) as Map<String, dynamic>;
} catch (e) {
log.warn(
'presubmit-guard-update: Failed to decode json data: ${message.data}',
e,
);
return Response.emptyOk;
}

final guardDocumentName = messageJson['guard_document_name'] as String?;
if (guardDocumentName == null || guardDocumentName.isEmpty) {
log.warn(
'presubmit-guard-update: Missing guard_document_name in message data.',
);
return Response.emptyOk;
}

try {
await _scheduler.processPresubmitGuardUpdate(guardDocumentName);
} catch (e, s) {
log.error(
'presubmit-guard-update: Error updating PresubmitGuard $guardDocumentName',
e,
s,
);
rethrow;
}

return Response.emptyOk;
}
}
Loading
Loading