Skip to content

Commit c4fad77

Browse files
committed
added "Log Analisis" tab
1 parent 1ec392b commit c4fad77

3 files changed

Lines changed: 157 additions & 12 deletions

File tree

dashboard/lib/service/data_seeder.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,13 @@ class DataSeeder {
501501
},
502502
startTime: creationTime + 30000,
503503
endTime: creationTime + 60000,
504+
logAnalysis: switch (status) {
505+
.failed =>
506+
'Based on my analysis of the provided LUCI logs and the context of the changes in this PR, here is the breakdown of the build failure:\n\n ### 1. Identify the specific test or command that failed for $jobName \n...',
507+
.infraFailure =>
508+
'Based on my analysis of the provided LUCI logs and the context of the changes in this PR, here is the breakdown of the build failure:\n\n ### 1. Identify the specific infra issue for $jobName \n...',
509+
_ => null,
510+
},
504511
);
505512
}
506513

dashboard/lib/views/presubmit_view.dart

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -328,18 +328,26 @@ class _JobDetailsViewerPane extends StatefulWidget {
328328

329329
class _JobDetailsViewerPaneState extends State<_JobDetailsViewerPane> {
330330
int _selectedAttemptIndex = 0;
331+
int _selectedDetailTabIndex = 0;
332+
String? _lastJobName;
331333

332334
@override
333335
Widget build(BuildContext context) {
334336
final theme = Theme.of(context);
335337
final isDark = theme.brightness == Brightness.dark;
336338
final presubmitState = Provider.of<PresubmitState>(context);
339+
final jobName = presubmitState.selectedJob;
340+
341+
if (_lastJobName != jobName) {
342+
_lastJobName = jobName;
343+
_selectedAttemptIndex = 0;
344+
_selectedDetailTabIndex = 0;
345+
}
337346

338347
return AnimatedBuilder(
339348
animation: presubmitState,
340349
builder: (context, _) {
341350
final repo = presubmitState.repo;
342-
final jobName = presubmitState.selectedJob;
343351
final jobs = presubmitState.jobs;
344352
final isLoading = presubmitState.isLoading;
345353

@@ -360,6 +368,7 @@ class _JobDetailsViewerPaneState extends State<_JobDetailsViewerPane> {
360368
}
361369

362370
final selectedJob = jobs[_selectedAttemptIndex];
371+
final hasLogAnalysis = selectedJob.logAnalysis != null && selectedJob.logAnalysis!.isNotEmpty;
363372

364373
return Column(
365374
crossAxisAlignment: CrossAxisAlignment.start,
@@ -448,22 +457,33 @@ class _JobDetailsViewerPaneState extends State<_JobDetailsViewerPane> {
448457
],
449458
),
450459
),
451-
const Padding(
452-
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
460+
461+
Container(
462+
height: 40,
463+
padding: const EdgeInsets.symmetric(horizontal: 24),
464+
decoration: BoxDecoration(
465+
color: theme.scaffoldBackgroundColor,
466+
border: Border(bottom: BorderSide(color: borderColor)),
467+
),
453468
child: Row(
454469
children: [
455-
Text(
456-
'Execution Details',
457-
style: TextStyle(fontWeight: FontWeight.w600),
458-
),
459-
Spacer(),
460-
Text(
470+
if (hasLogAnalysis) ...[
471+
_buildDetailTab('Log Analysis', 0, isDark),
472+
_buildDetailTab('Execution Details', 1, isDark),
473+
] else
474+
const Text(
475+
'Execution Details',
476+
style: TextStyle(fontWeight: FontWeight.w600),
477+
),
478+
const Spacer(),
479+
const Text(
461480
'Raw output',
462481
style: TextStyle(fontSize: 12, color: Colors.grey),
463482
),
464483
],
465484
),
466485
),
486+
const SizedBox(height: 12),
467487
Expanded(
468488
child: Container(
469489
margin: const EdgeInsets.symmetric(horizontal: 24),
@@ -476,9 +496,11 @@ class _JobDetailsViewerPaneState extends State<_JobDetailsViewerPane> {
476496
width: double.infinity,
477497
child: SingleChildScrollView(
478498
child: Text(
479-
selectedJob.summary?.trim().isEmpty ?? true
480-
? _getDefaultJobDetails(selectedJob)
481-
: selectedJob.summary!,
499+
hasLogAnalysis && _selectedDetailTabIndex == 0
500+
? selectedJob.logAnalysis!
501+
: (selectedJob.summary?.trim().isEmpty ?? true
502+
? _getDefaultJobDetails(selectedJob)
503+
: selectedJob.summary!),
482504
style: const TextStyle(
483505
fontFamily: 'monospace',
484506
fontSize: 13,
@@ -534,6 +556,35 @@ class _JobDetailsViewerPaneState extends State<_JobDetailsViewerPane> {
534556
);
535557
}
536558

559+
Widget _buildDetailTab(String label, int index, bool isDark) {
560+
final isSelected = _selectedDetailTabIndex == index;
561+
return InkWell(
562+
onTap: () => setState(() => _selectedDetailTabIndex = index),
563+
child: Container(
564+
padding: const EdgeInsets.symmetric(horizontal: 16),
565+
alignment: Alignment.center,
566+
decoration: BoxDecoration(
567+
border: Border(
568+
bottom: BorderSide(
569+
color: isSelected ? const Color(0xFF3B82F6) : Colors.transparent,
570+
width: 2,
571+
),
572+
),
573+
),
574+
child: Text(
575+
label,
576+
style: TextStyle(
577+
fontSize: 13,
578+
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
579+
color: isSelected
580+
? (isDark ? Colors.white : Colors.black)
581+
: (isDark ? const Color(0xFF8B949E) : const Color(0xFF6B7280)),
582+
),
583+
),
584+
),
585+
);
586+
}
587+
537588
String _getDefaultJobDetails(PresubmitJobResponse job) {
538589
return switch (job.status) {
539590
.succeeded =>

dashboard/test/views/presubmit_view_test.dart

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,93 @@ void main() {
370370
},
371371
);
372372

373+
testWidgets('PreSubmitView displays Log Analysis tab when present', (
374+
WidgetTester tester,
375+
) async {
376+
tester.view.physicalSize = const Size(2000, 1080);
377+
tester.view.devicePixelRatio = 1.0;
378+
addTearDown(tester.view.resetPhysicalSize);
379+
addTearDown(tester.view.resetDevicePixelRatio);
380+
381+
const mockSha = 'decaf_3_real_sha';
382+
const guardResponse = PresubmitGuardResponse(
383+
prNum: 123,
384+
author: 'dash',
385+
guardStatus: GuardStatus.failed,
386+
checkRunId: 456,
387+
stages: [
388+
PresubmitGuardStage(
389+
name: 'Engine',
390+
createdAt: 0,
391+
builds: {'Mac mac_host_engine 1': TaskStatus.failed},
392+
),
393+
],
394+
);
395+
396+
when(
397+
mockCocoonService.fetchPresubmitGuard(
398+
repo: anyNamed('repo'),
399+
sha: mockSha,
400+
),
401+
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
402+
403+
when(
404+
mockCocoonService.fetchPresubmitJobDetails(
405+
checkRunId: anyNamed('checkRunId'),
406+
jobName: argThat(contains('mac_host_engine'), named: 'jobName'),
407+
),
408+
).thenAnswer(
409+
(_) async => CocoonResponse.data([
410+
PresubmitJobResponse(
411+
attemptNumber: 1,
412+
jobName: 'Mac mac_host_engine 1',
413+
creationTime: 0,
414+
status: TaskStatus.failed,
415+
summary: 'Test failed: Unit Tests',
416+
logAnalysis: 'Found 2 flakiness candidates',
417+
),
418+
]),
419+
);
420+
421+
await tester.runAsync(() async {
422+
await tester.pumpWidget(
423+
createPreSubmitView({'repo': 'flutter', 'pr': '123'}),
424+
);
425+
for (var i = 0; i < 50; i++) {
426+
await tester.pump();
427+
await Future<void>.delayed(const Duration(milliseconds: 50));
428+
if (find.textContaining('by dash').evaluate().isNotEmpty) break;
429+
}
430+
});
431+
await tester.pumpAndSettle();
432+
433+
expect(find.textContaining('PR #123'), findsOneWidget);
434+
435+
await tester.tap(find.textContaining('mac_host_engine').first);
436+
await tester.runAsync(() async {
437+
for (var i = 0; i < 50; i++) {
438+
await tester.pump();
439+
await Future<void>.delayed(const Duration(milliseconds: 50));
440+
if (find.textContaining('Found 2 flakiness candidates').evaluate().isNotEmpty) {
441+
break;
442+
}
443+
}
444+
});
445+
await tester.pumpAndSettle();
446+
447+
// Verify Log Analysis content is shown by default
448+
expect(find.textContaining('Found 2 flakiness candidates'), findsOneWidget);
449+
expect(find.text('Log Analysis'), findsOneWidget);
450+
expect(find.text('Execution Details'), findsOneWidget);
451+
452+
// Switch to Execution Details tab
453+
await tester.tap(find.text('Execution Details'));
454+
await tester.pumpAndSettle();
455+
456+
// Verify Execution Details content is shown
457+
expect(find.textContaining('Test failed: Unit Tests'), findsOneWidget);
458+
});
459+
373460
testWidgets(
374461
'PreSubmitView automatically selects latest SHA and updates sidebar when opened with PR only',
375462
(WidgetTester tester) async {

0 commit comments

Comments
 (0)