Skip to content
Closed
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
7 changes: 7 additions & 0 deletions app_dart/bin/gae_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import 'package:cocoon_service/src/service/content_aware_hash_service.dart';
import 'package:cocoon_service/src/service/firebase_jwt_validator.dart';
import 'package:cocoon_service/src/service/flags/dynamic_config_updater.dart';
import 'package:cocoon_service/src/service/get_files_changed.dart';
import 'package:cocoon_service/src/service/log_analyzer.dart';
import 'package:cocoon_service/src/service/scheduler/ci_yaml_fetcher.dart';
import 'package:genkit/genkit.dart';
import 'package:genkit_google_genai/genkit_google_genai.dart';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';

Expand Down Expand Up @@ -60,6 +63,9 @@ Future<void> main() async {
// every ~1 minute.
configUpdater.startUpdateLoop(config);

final geminiKey = await config.geminiLogAnalyzerKey;
final ai = Genkit(plugins: [googleAI(apiKey: geminiKey)]);

final firebaseJwtValidator = FirebaseJwtValidator(cache: cache);
final dashboardAuthProvider = DashboardAuthentication(
cache: cache,
Expand Down Expand Up @@ -158,6 +164,7 @@ Future<void> main() async {
ciYamlFetcher: ciYamlFetcher,
buildStatusService: buildStatusService,
contentAwareHashService: contentHashService,
logAnalyzer: GenkitLogAnalyzer(ai, modelName: config.flags.geminiModel),
);

return runAppEngine(
Expand Down
3 changes: 3 additions & 0 deletions app_dart/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ closeMqGuardAfterPresubmit: true
# Whether to allow the tree status to be suppressed for specific failed tests.
dynamicTestSuppression: true

# The Gemini model to use for log analysis.
geminiModel: gemini-3-flash-preview

# Whether to allow unified check run flow to specific users or to everyone.
unifiedCheckRunFlow:
useForAll: false
Expand Down
10 changes: 10 additions & 0 deletions app_dart/lib/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:math';

import 'cocoon_service.dart';
import 'src/request_handlers/analyze_logs.dart';
import 'src/request_handlers/get_engine_artifacts_ready.dart';
import 'src/request_handlers/get_presubmit_guard.dart';
import 'src/request_handlers/get_presubmit_guard_summaries.dart';
Expand All @@ -24,6 +25,7 @@ import 'src/service/build_status_service.dart';
import 'src/service/commit_service.dart';
import 'src/service/content_aware_hash_service.dart';
import 'src/service/discord_service.dart';
import 'src/service/log_analyzer.dart';
import 'src/service/scheduler/ci_yaml_fetcher.dart';
import 'src/service/test_suppression.dart';

Expand All @@ -48,6 +50,7 @@ Server createServer({
required BuildStatusService buildStatusService,
required ContentAwareHashService contentAwareHashService,
required AuthenticationProvider presubmitAuthProvider,
required LogAnalyzer logAnalyzer,
}) {
final githubWebhook = GithubWebhook(
config: config,
Expand All @@ -63,6 +66,13 @@ Server createServer({
);

final handlers = <String, RequestHandler>{
'/api/analyze-logs': AnalyzeLogs(
config: config,
authenticationProvider: presubmitAuthProvider,
luciBuildService: luciBuildService,
firestore: firestore,
logAnalyzer: logAnalyzer,
),
'/api/create-branch': CreateBranch(
branchService: branchService,
config: config,
Expand Down
3 changes: 3 additions & 0 deletions app_dart/lib/src/generated_config.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 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 @@ -41,6 +41,7 @@ class PresubmitCompletedJob {
final int? endTime;
final String? summary;
final int? buildNumber;
final int? buildId;

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

/// Creates a [PresubmitCompletedJob] from a GitHub [CheckRun].
Expand All @@ -81,6 +83,7 @@ class PresubmitCompletedJob {
endTime: null,
summary: null,
buildNumber: null,
buildId: null,
);
}

Expand All @@ -107,6 +110,7 @@ class PresubmitCompletedJob {
endTime: build.endTime.toDateTime().millisecondsSinceEpoch,
summary: build.summaryMarkdown,
buildNumber: build.number,
buildId: build.id.toInt(),
);
}

Expand Down Expand Up @@ -149,6 +153,7 @@ class PresubmitCompletedJob {
endTime: endTime,
summary: summary,
buildNumber: buildNumber,
buildId: buildId,
);
}

Expand Down
3 changes: 3 additions & 0 deletions app_dart/lib/src/model/common/presubmit_job_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class PresubmitJobState {
final int? endTime;
final String? summary;
final int? buildNumber;
final int? buildId;

const PresubmitJobState({
required this.jobName,
Expand All @@ -29,6 +30,7 @@ class PresubmitJobState {
this.endTime,
this.summary,
this.buildNumber,
this.buildId,
});
}

Expand All @@ -41,5 +43,6 @@ extension BuildToPresubmitJobState on bbv2.Build {
endTime: endTime.toDateTime().millisecondsSinceEpoch,
summary: summaryMarkdown,
buildNumber: number,
buildId: id.toInt(),
);
}
29 changes: 29 additions & 0 deletions app_dart/lib/src/model/firestore/presubmit_job.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,14 @@ final class PresubmitJob extends AppDocument<PresubmitJob> {
static const fieldSlug = 'slug';
static const fieldJobName = 'job_name';
static const fieldBuildNumber = 'build_number';
static const fieldBuildId = 'build_id';
static const fieldStatus = 'status';
static const fieldAttemptNumber = 'attempt_number';
static const fieldCreationTime = 'creation_time';
static const fieldStartTime = 'start_time';
static const fieldEndTime = 'end_time';
static const fieldSummary = 'summary';
static const fieldLogAnalysis = 'log_analysis';

static AppDocumentId<PresubmitJob> documentIdFor({
required RepositorySlug slug,
Expand Down Expand Up @@ -168,22 +170,26 @@ final class PresubmitJob extends AppDocument<PresubmitJob> {
required int attemptNumber,
required int creationTime,
int? buildNumber,
int? buildId,
int? startTime,
int? endTime,
String? summary,
String? logAnalysis,
}) {
return PresubmitJob._(
{
fieldSlug: slug.fullName.toValue(),
fieldCheckRunId: checkRunId.toValue(),
fieldJobName: jobName.toValue(),
fieldBuildNumber: ?buildNumber?.toValue(),
fieldBuildId: ?buildId?.toValue(),
fieldStatus: status.value.toValue(),
fieldAttemptNumber: attemptNumber.toValue(),
fieldCreationTime: creationTime.toValue(),
fieldStartTime: ?startTime?.toValue(),
fieldEndTime: ?endTime?.toValue(),
fieldSummary: ?summary?.toValue(),
fieldLogAnalysis: ?logAnalysis?.toValue(),
},
name: documentNameFor(
slug: slug,
Expand Down Expand Up @@ -213,9 +219,11 @@ final class PresubmitJob extends AppDocument<PresubmitJob> {
creationTime: creationTime,
status: TaskStatus.waitingForBackfill,
buildNumber: null,
buildId: null,
startTime: null,
endTime: null,
summary: null,
logAnalysis: null,
);
}

Expand All @@ -240,13 +248,17 @@ final class PresubmitJob extends AppDocument<PresubmitJob> {
int? get buildNumber => fields[fieldBuildNumber] != null
? int.parse(fields[fieldBuildNumber]!.integerValue!)
: null;
int? get buildId => fields[fieldBuildId] != null
? int.parse(fields[fieldBuildId]!.integerValue!)
: null;
Comment on lines +251 to +253
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: Should you use int.tryParse just to avoid throwing if the field is unparsable?
you could also cheat and just do: int.tryParse(fields[fieldBuildId]?.integerValue ?? '')

int? get startTime => fields[fieldStartTime] != null
? int.parse(fields[fieldStartTime]!.integerValue!)
: null;
int? get endTime => fields[fieldEndTime] != null
? int.parse(fields[fieldEndTime]!.integerValue!)
: null;
String? get summary => fields[fieldSummary]?.stringValue;
String? get logAnalysis => fields[fieldLogAnalysis]?.stringValue;

TaskStatus get status {
final rawValue = fields[fieldStatus]!.stringValue!;
Expand All @@ -273,6 +285,14 @@ final class PresubmitJob extends AppDocument<PresubmitJob> {
}
}

set buildId(int? buildId) {
if (buildId == null) {
fields.remove(fieldBuildId);
} else {
fields[fieldBuildId] = buildId.toValue();
}
}

set summary(String? summary) {
if (summary == null) {
fields.remove(fieldSummary);
Expand All @@ -281,8 +301,17 @@ final class PresubmitJob extends AppDocument<PresubmitJob> {
}
}

set logAnalysis(String? logAnalysis) {
if (logAnalysis == null) {
fields.remove(fieldLogAnalysis);
} else {
fields[fieldLogAnalysis] = logAnalysis.toValue();
}
}

void updateFromBuild(bbv2.Build build) {
fields[fieldBuildNumber] = build.number.toValue();
fields[fieldBuildId] = Value(integerValue: build.id.toString());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fields[fieldBuildId] = Value(integerValue: build.id.toString());
fields[fieldBuildId] = build.id.toValue();

fields[fieldCreationTime] = build.createTime
.toDateTime()
.millisecondsSinceEpoch
Expand Down
Loading
Loading