Read the PR diff and build logs and feed it to gemini in prompt (#5137)

https://github.com/flutter/flutter/issues/191099
diff --git a/app_dart/lib/src/request_handlers/analyze_logs.dart b/app_dart/lib/src/request_handlers/analyze_logs.dart
index c9e4b00..42d580c 100644
--- a/app_dart/lib/src/request_handlers/analyze_logs.dart
+++ b/app_dart/lib/src/request_handlers/analyze_logs.dart
@@ -3,12 +3,14 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:convert';
 
 import 'package:buildbucket/buildbucket_pb.dart' as bbv2;
 import 'package:cocoon_server/logging.dart' show log;
 import 'package:collection/collection.dart';
 import 'package:fixnum/fixnum.dart';
 import 'package:github/github.dart';
+import 'package:http/http.dart' as http;
 import 'package:retry/retry.dart';
 
 import '../../cocoon_service.dart';
@@ -92,8 +94,10 @@
       );
     }
 
-    final (:stdoutLogs, :build) = await getBuildStepLogs(buildId: buildId);
-    if (stdoutLogs.isEmpty) {
+    final (stdoutLogs: logsUrls, :build) = await getBuildStepLogs(
+      buildId: buildId,
+    );
+    if (logsUrls.isEmpty) {
       throw NotFoundException('Logs Not Found for BuildId: $buildId');
     }
     String prDiffUrl;
@@ -108,13 +112,36 @@
     }
 
     log.info(
-      '$logCrumb For PR diff url: $prDiffUrl Analyzing log urls: ${stdoutLogs.join(',')}',
+      '$logCrumb For PR diff url: $prDiffUrl Analyzing log urls: ${logsUrls.join(',')}',
     );
+    String prDiff;
+    try {
+      prDiff = await http
+          .read(Uri.parse(prDiffUrl))
+          .timeout(const Duration(seconds: 30));
+    } catch (e, stackTrace) {
+      log.error('Failed to read PR diff from $prDiffUrl.', e, stackTrace);
+      rethrow;
+    }
+
+    final logs = <String>[];
+    for (final logUrl in logsUrls) {
+      final rawLogUrl = '$logUrl?format=raw';
+      try {
+        final logContent = await http
+            .read(Uri.parse(rawLogUrl))
+            .timeout(const Duration(seconds: 30));
+        logs.add(logContent);
+      } catch (e, stackTrace) {
+        log.error('Failed to read log from $rawLogUrl.', e, stackTrace);
+        rethrow;
+      }
+    }
 
     // 4. Feed text to genkit.
     final prompt =
-        '''You are a Senior Infrastructure Engineer specializing in the Flutter CI ecosystem.
-I will provide you with a link to github pull request and the logs of a failed build step in a LUCI build associated with that change.
+        '''You are a Senior Infrastructure Engineer specializing in the Flutter and Flutter Packages CI ecosystem.
+In a Context section there are github pull request diff and the logs of a failed build step of a LUCI build associated with that change.
 
 ## Your task
 
@@ -172,10 +199,13 @@
 - Linker error messages (e.g., `undefined reference to`).
 - Summary messages in the check-runs API output like `1 build failed: [<build_name>]`.
 
-## Links
-
-Link to GitHub Pull Request Diff: $prDiffUrl
-Links to Logs: ${stdoutLogs.join('\n')}
+## Context
+<PULL_REQUEST_DIFF>
+${jsonEncode(prDiff)}
+</PULL_REQUEST_DIFF>
+<LOGS>
+${jsonEncode(logs)}
+</LOGS>
 ''';
 
     final analysis = await _logAnalyzer.analyze(prompt: prompt);
@@ -220,7 +250,7 @@
         if (step.logs.isNotEmpty) {
           for (final log in step.logs) {
             if (log.name == 'stdout') {
-              stdoutLogs.add(log.hasViewUrl() ? log.viewUrl : log.url);
+              stdoutLogs.add(log.viewUrl);
             }
           }
         } else if (kSubbuildPattern.hasMatch(step.summaryMarkdown)) {
diff --git a/app_dart/test/request_handlers/analyze_logs_test.dart b/app_dart/test/request_handlers/analyze_logs_test.dart
index f5a3856..6459c88 100644
--- a/app_dart/test/request_handlers/analyze_logs_test.dart
+++ b/app_dart/test/request_handlers/analyze_logs_test.dart
@@ -13,6 +13,8 @@
 import 'package:cocoon_service/src/request_handling/exceptions.dart';
 import 'package:cocoon_service/src/service/log_analyzer.dart';
 import 'package:fixnum/fixnum.dart';
+import 'package:http/http.dart' as http;
+import 'package:http/testing.dart';
 import 'package:mockito/mockito.dart';
 import 'package:test/test.dart';
 
@@ -100,7 +102,8 @@
           ..logs.addAll([
             bbv2.Log.create()
               ..name = 'stdout'
-              ..url = 'http://logs/stdout',
+              ..url = 'http://logs/stdout'
+              ..viewUrl = 'http://logs/stdout',
           ]),
       ])
       ..tags.addAll([
@@ -123,7 +126,20 @@
       'build_id': '9223372036854775807',
     };
 
-    final response = await tester.post(handler);
+    final mockHttpClient = MockClient((request) async {
+      if (request.url.toString() == 'http://github/pr/1.diff') {
+        return http.Response('fake diff', 200);
+      }
+      if (request.url.toString() == 'http://logs/stdout?format=raw') {
+        return http.Response('fake log content', 200);
+      }
+      return http.Response('not found', 404);
+    });
+
+    final response = await http.runWithClient(
+      () => tester.post(handler),
+      () => mockHttpClient,
+    );
     expect(response.statusCode, HttpStatus.ok);
 
     final updatedCheck = PresubmitJob.fromDocument(