diff --git a/app_dart/lib/src/request_handlers/get_presubmit_jobs.dart b/app_dart/lib/src/request_handlers/get_presubmit_jobs.dart index 5d7b6af56..b770dc8a0 100644 --- a/app_dart/lib/src/request_handlers/get_presubmit_jobs.dart +++ b/app_dart/lib/src/request_handlers/get_presubmit_jobs.dart @@ -97,7 +97,7 @@ final class GetPresubmitJobs extends PublicApiRequestHandler { creationTime: job.creationTime, startTime: job.startTime, endTime: job.endTime, - status: job.status.value, + status: job.status, summary: job.summary, buildNumber: job.buildNumber, ), diff --git a/app_dart/test/request_handlers/get_presubmit_checks_test.dart b/app_dart/test/request_handlers/get_presubmit_checks_test.dart index 179c13087..420609091 100644 --- a/app_dart/test/request_handlers/get_presubmit_checks_test.dart +++ b/app_dart/test/request_handlers/get_presubmit_checks_test.dart @@ -98,7 +98,7 @@ void main() { expect(jobs.length, 1); expect(jobs[0].attemptNumber, 1); expect(jobs[0].jobName, 'linux'); - expect(jobs[0].status, 'Succeeded'); + expect(jobs[0].status, TaskStatus.succeeded); expect(jobs[0].buildNumber, 456); }); diff --git a/dashboard/lib/state/presubmit.dart b/dashboard/lib/state/presubmit.dart index 005ddfb37..c491381f3 100644 --- a/dashboard/lib/state/presubmit.dart +++ b/dashboard/lib/state/presubmit.dart @@ -322,14 +322,14 @@ class PresubmitState extends ChangeNotifier { this.repo = repo; changed = true; } - if (pr != this.pr) { + if (this.pr != pr) { this.pr = pr; changed = true; _availableSummaries = []; _lastFetchedPr = null; clearFilters(); } - if (sha != this.sha) { + if (this.sha != sha) { this.sha = sha; changed = true; _guardResponse = null; diff --git a/dashboard/lib/views/presubmit_view.dart b/dashboard/lib/views/presubmit_view.dart index ccf50ddb4..d0545a205 100644 --- a/dashboard/lib/views/presubmit_view.dart +++ b/dashboard/lib/views/presubmit_view.dart @@ -24,7 +24,7 @@ import '../widgets/task_box.dart'; /// A detailed monitoring view for a specific Pull Request (PR) or commit SHA. /// -/// This view displays CI job statuses and execution logs. +/// This view displays CI job statuses and execution details. final class PreSubmitView extends StatefulWidget { const PreSubmitView({ super.key, @@ -284,9 +284,11 @@ class _PreSubmitViewState extends State { child: (selectedJob == null || guardResponse == null) ? const Center( - child: Text('Select a job to view logs'), + child: Text( + 'Select a job to view execution details.', + ), ) - : const _LogViewerPane(), + : const _JobDetailsViewerPane(), ), ], ), @@ -300,14 +302,14 @@ class _PreSubmitViewState extends State { } } -class _LogViewerPane extends StatefulWidget { - const _LogViewerPane(); +class _JobDetailsViewerPane extends StatefulWidget { + const _JobDetailsViewerPane(); @override - State<_LogViewerPane> createState() => _LogViewerPaneState(); + State<_JobDetailsViewerPane> createState() => _JobDetailsViewerPaneState(); } -class _LogViewerPaneState extends State<_LogViewerPane> { +class _JobDetailsViewerPaneState extends State<_JobDetailsViewerPane> { int _selectedAttemptIndex = 0; @override @@ -434,7 +436,7 @@ class _LogViewerPaneState extends State<_LogViewerPane> { child: Row( children: [ Text( - 'Execution Log', + 'Execution Details', style: TextStyle(fontWeight: FontWeight.w600), ), Spacer(), @@ -457,7 +459,7 @@ class _LogViewerPaneState extends State<_LogViewerPane> { width: double.infinity, child: SingleChildScrollView( child: Text( - selectedJob.summary ?? 'No log summary available', + selectedJob.summary ?? _getDefaultJobDetails(selectedJob), style: const TextStyle( fontFamily: 'monospace', fontSize: 13, @@ -512,6 +514,24 @@ class _LogViewerPaneState extends State<_LogViewerPane> { }, ); } + + String _getDefaultJobDetails(PresubmitJobResponse job) { + return switch (job.status) { + .succeeded => + '${job.jobName} executed successfully.\nClick "View more details on LUCI UI" button bellow for more details.', + .failed => + '${job.jobName} failed.\nClick "View more details on LUCI UI" button bellow for more details.', + .infraFailure => + 'Infrastructure failed during execution of ${job.jobName}.\nClick "View more details on LUCI UI" button bellow for more details.', + .skipped => '${job.jobName} is skipped.', + .neutral => '${job.jobName} is disabled.', + .cancelled => '${job.jobName} is cancelled.', + .inProgress => + '${job.jobName} is in progress.\nClick "View more details on LUCI UI" button bellow to see execution details.', + .waitingForBackfill => + '${job.jobName} is not yet scheduled for execution.\n"View more details on LUCI UI" button will become enabled once the job is scheduled.', + }; + } } class _JobsSidebar extends StatefulWidget { diff --git a/dashboard/test/integration/get_presubmit_checks_test.dart b/dashboard/test/integration/get_presubmit_checks_test.dart index cb48bcc36..32fdcd758 100644 --- a/dashboard/test/integration/get_presubmit_checks_test.dart +++ b/dashboard/test/integration/get_presubmit_checks_test.dart @@ -59,7 +59,7 @@ void main() { final result = response.data!.first; expect(result.jobName, 'linux android_2'); - expect(result.status, 'Succeeded'); + expect(result.status, TaskStatus.succeeded); expect(result.attemptNumber, 1); expect(result.buildNumber, 1337); expect(result.summary, 'Build succeeded'); @@ -107,10 +107,10 @@ void main() { // `orderMap: const { PresubmitJob.fieldAttemptNumber: kQueryOrderDescending }` expect(response.data![0].attemptNumber, 2); - expect(response.data![0].status, 'Succeeded'); + expect(response.data![0].status, TaskStatus.succeeded); expect(response.data![1].attemptNumber, 1); - expect(response.data![1].status, 'Failed'); + expect(response.data![1].status, TaskStatus.failed); }); }); } diff --git a/dashboard/test/logic/presubmit_guard_test.dart b/dashboard/test/logic/presubmit_guard_test.dart index 825f1bb97..a8853ac5b 100644 --- a/dashboard/test/logic/presubmit_guard_test.dart +++ b/dashboard/test/logic/presubmit_guard_test.dart @@ -43,7 +43,7 @@ void main() { 'attempt_number': 1, 'job_name': 'Linux Device Doctor', 'creation_time': 1620134239000, - 'status': 'Succeeded', + 'status': '${TaskStatus.succeeded}', 'summary': 'Check passed', }; @@ -52,7 +52,7 @@ void main() { expect(response.attemptNumber, 1); expect(response.jobName, 'Linux Device Doctor'); expect(response.creationTime, 1620134239000); - expect(response.status, 'Succeeded'); + expect(response.status, TaskStatus.succeeded); expect(response.summary, 'Check passed'); }); }); diff --git a/dashboard/test/state/presubmit_test.dart b/dashboard/test/state/presubmit_test.dart index e0e68ae13..32abef494 100644 --- a/dashboard/test/state/presubmit_test.dart +++ b/dashboard/test/state/presubmit_test.dart @@ -4,6 +4,7 @@ import 'package:cocoon_common/guard_status.dart'; import 'package:cocoon_common/rpc_model.dart'; +import 'package:cocoon_common/task_status.dart'; import 'package:flutter_dashboard/service/cocoon.dart'; import 'package:flutter_dashboard/state/presubmit.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -159,7 +160,7 @@ void main() { attemptNumber: 1, jobName: 'check1', creationTime: 0, - status: 'Succeeded', + status: TaskStatus.succeeded, ), ]; const guardResponse = PresubmitGuardResponse( @@ -395,6 +396,8 @@ void main() { presubmitState.update(pr: '123', sha: null); + await Future.delayed(Duration.zero); + verify( mockCocoonService.fetchPresubmitGuardSummaries( pr: '123', diff --git a/dashboard/test/views/presubmit_view_test.dart b/dashboard/test/views/presubmit_view_test.dart index 49f493eb0..8b08d8899 100644 --- a/dashboard/test/views/presubmit_view_test.dart +++ b/dashboard/test/views/presubmit_view_test.dart @@ -232,14 +232,14 @@ void main() { attemptNumber: 1, jobName: 'Mac mac_host_engine 1', creationTime: 0, - status: 'Succeeded', + status: TaskStatus.succeeded, summary: 'All tests passed (452/452)', ), PresubmitJobResponse( attemptNumber: 2, jobName: 'Mac mac_host_engine 1', creationTime: 0, - status: 'Failed', + status: TaskStatus.failed, summary: 'Test failed: Unit Tests', ), ]), @@ -415,7 +415,7 @@ void main() { attemptNumber: 1, jobName: 'Mac mac_host_engine', creationTime: 0, - status: 'Succeeded', + status: TaskStatus.succeeded, summary: 'Live log content', ), ]), diff --git a/packages/cocoon_common/lib/src/rpc_model/presubmit_job_response.dart b/packages/cocoon_common/lib/src/rpc_model/presubmit_job_response.dart index 9f9325d07..a9543c285 100644 --- a/packages/cocoon_common/lib/src/rpc_model/presubmit_job_response.dart +++ b/packages/cocoon_common/lib/src/rpc_model/presubmit_job_response.dart @@ -5,6 +5,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; +import '../../task_status.dart'; import 'base.dart'; part 'presubmit_job_response.g.dart'; @@ -54,7 +55,7 @@ final class PresubmitJobResponse extends Model { final int? endTime; /// The status of the job. - final String status; + final TaskStatus status; /// A brief summary of the job result or link to logs. final String? summary; diff --git a/packages/cocoon_common/lib/src/rpc_model/presubmit_job_response.g.dart b/packages/cocoon_common/lib/src/rpc_model/presubmit_job_response.g.dart index 14fd88288..c26621f0e 100644 --- a/packages/cocoon_common/lib/src/rpc_model/presubmit_job_response.g.dart +++ b/packages/cocoon_common/lib/src/rpc_model/presubmit_job_response.g.dart @@ -21,7 +21,10 @@ PresubmitJobResponse _$PresubmitJobResponseFromJson( creationTime: $checkedConvert('creation_time', (v) => (v as num).toInt()), startTime: $checkedConvert('start_time', (v) => (v as num?)?.toInt()), endTime: $checkedConvert('end_time', (v) => (v as num?)?.toInt()), - status: $checkedConvert('status', (v) => v as String), + status: $checkedConvert( + 'status', + (v) => $enumDecode(_$TaskStatusEnumMap, v), + ), summary: $checkedConvert('summary', (v) => v as String?), buildNumber: $checkedConvert('build_number', (v) => (v as num?)?.toInt()), ); @@ -49,3 +52,14 @@ Map _$PresubmitJobResponseToJson( 'summary': ?instance.summary, 'build_number': ?instance.buildNumber, }; + +const _$TaskStatusEnumMap = { + TaskStatus.cancelled: 'Cancelled', + TaskStatus.waitingForBackfill: 'New', + TaskStatus.inProgress: 'In Progress', + TaskStatus.infraFailure: 'Infra Failure', + TaskStatus.failed: 'Failed', + TaskStatus.succeeded: 'Succeeded', + TaskStatus.neutral: 'Neutral', + TaskStatus.skipped: 'Skipped', +}; diff --git a/packages/cocoon_common/test/presubmit_check_response_test.dart b/packages/cocoon_common/test/presubmit_check_response_test.dart index 566cd8816..e42cbaa76 100644 --- a/packages/cocoon_common/test/presubmit_check_response_test.dart +++ b/packages/cocoon_common/test/presubmit_check_response_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:cocoon_common/rpc_model.dart'; +import 'package:cocoon_common/task_status.dart'; import 'package:test/test.dart'; void main() { @@ -12,7 +13,7 @@ void main() { 'attempt_number': 1, 'job_name': 'linux', 'creation_time': 1000, - 'status': 'succeeded', + 'status': '${TaskStatus.succeeded}', 'build_number': 456, }; @@ -28,7 +29,7 @@ void main() { 'attempt_number': 1, 'job_name': 'linux', 'creation_time': 1000, - 'status': 'succeeded', + 'status': '${TaskStatus.succeeded}', }; final response = PresubmitJobResponse.fromJson(json);