Skip to content

Commit 047ea80

Browse files
committed
feat: Add a shared utility for generating LUCI build log URLs and integrate it into the presubmit view and task attempt summary widget.
1 parent 1220d8e commit 047ea80

6 files changed

Lines changed: 99 additions & 40 deletions

File tree

dashboard/lib/service/dev_cocoon.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ class DevelopmentCocoonService implements CocoonService {
313313
buildName: buildName,
314314
creationTime: now.millisecondsSinceEpoch - 10000,
315315
status: 'Succeeded',
316+
buildNumber: 12345,
316317
summary:
317318
'''
318319
[INFO] Starting task $buildName...
@@ -326,6 +327,7 @@ class DevelopmentCocoonService implements CocoonService {
326327
buildName: buildName,
327328
creationTime: now.millisecondsSinceEpoch,
328329
status: 'Failed',
330+
buildNumber: 67890,
329331
summary:
330332
'[INFO] Starting task $buildName...\n[ERROR] Test failed: Unit Tests',
331333
),

dashboard/lib/views/presubmit_view.dart

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
import 'dart:async';
66

7+
import 'package:cocoon_common/build_log_url.dart';
78
import 'package:cocoon_common/guard_status.dart';
89
import 'package:cocoon_common/rpc_model.dart';
910
import 'package:cocoon_common/task_status.dart';
1011
import 'package:flutter/material.dart';
1112
import 'package:flutter/services.dart';
1213
import 'package:provider/provider.dart';
14+
import 'package:url_launcher/url_launcher.dart';
1315

1416
import '../dashboard_navigation_drawer.dart';
1517
import '../state/presubmit.dart';
@@ -422,24 +424,37 @@ class _LogViewerPaneState extends State<_LogViewerPane> {
422424
Padding(
423425
padding: const EdgeInsets.all(24.0),
424426
child: InkWell(
425-
onTap: () {},
427+
onTap: selectedCheck.buildNumber == null
428+
? null
429+
: () => launchUrl(
430+
Uri.parse(
431+
generateBuildLogUrl(
432+
buildName: selectedCheck.buildName,
433+
buildNumber: selectedCheck.buildNumber!,
434+
),
435+
),
436+
),
426437
child: Row(
427438
mainAxisSize: MainAxisSize.min,
428439
children: [
429440
Icon(
430441
Icons.open_in_new,
431442
size: 18,
432-
color: isDark
433-
? const Color(0xFF58A6FF)
434-
: const Color(0xFF0969DA),
443+
color: selectedCheck.buildNumber == null
444+
? Colors.grey
445+
: (isDark
446+
? const Color(0xFF58A6FF)
447+
: const Color(0xFF0969DA)),
435448
),
436449
const SizedBox(width: 8),
437450
Text(
438451
'View more details on LUCI UI',
439452
style: TextStyle(
440-
color: isDark
441-
? const Color(0xFF58A6FF)
442-
: const Color(0xFF0969DA),
453+
color: selectedCheck.buildNumber == null
454+
? Colors.grey
455+
: (isDark
456+
? const Color(0xFF58A6FF)
457+
: const Color(0xFF0969DA)),
443458
fontSize: 14,
444459
),
445460
),

dashboard/lib/widgets/luci_task_attempt_summary.dart

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'package:cocoon_common/is_dart_internal.dart';
5+
import 'package:cocoon_common/build_log_url.dart';
66
import 'package:cocoon_common/rpc_model.dart';
77
import 'package:flutter/material.dart';
88
import 'package:url_launcher/url_launcher.dart';
@@ -17,42 +17,23 @@ class LuciTaskAttemptSummary extends StatelessWidget {
1717
/// The task to show information from.
1818
final Task task;
1919

20-
@visibleForTesting
21-
static const String luciProdLogBase =
22-
'https://ci.chromium.org/p/flutter/builders';
23-
24-
@visibleForTesting
25-
static const String dartInternalLogBase =
26-
'https://ci.chromium.org/p/dart-internal/builders';
27-
2820
@override
2921
Widget build(BuildContext context) {
3022
return ListBody(
3123
children: List<Widget>.generate(task.buildNumberList.length, (int i) {
24+
final buildNumber = task.buildNumberList[i];
3225
return ElevatedButton(
33-
child: Text('OPEN LOG FOR BUILD #${task.buildNumberList[i]}'),
26+
child: Text('OPEN LOG FOR BUILD #$buildNumber'),
3427
onPressed: () async {
35-
if (isTaskFromDartInternalBuilder(builderName: task.builderName)) {
36-
await launchUrl(
37-
_dartInternalLogUrl(task.builderName, task.buildNumberList[i]),
38-
);
39-
} else {
40-
await launchUrl(
41-
_luciProdLogUrl(task.builderName, task.buildNumberList[i]),
42-
);
43-
}
28+
final url = generateBuildLogUrl(
29+
buildName: task.builderName,
30+
buildNumber: buildNumber,
31+
isBringup: task.isBringup,
32+
);
33+
await launchUrl(Uri.parse(url));
4434
},
4535
);
4636
}),
4737
);
4838
}
49-
50-
Uri _luciProdLogUrl(String builderName, int buildNumber) {
51-
final pool = task.isBringup ? 'staging' : 'prod';
52-
return Uri.parse('$luciProdLogBase/$pool/$builderName/$buildNumber');
53-
}
54-
55-
Uri _dartInternalLogUrl(String builderName, int buildNumber) {
56-
return Uri.parse('$dartInternalLogBase/flutter/$builderName/$buildNumber');
57-
}
5839
}

dashboard/test/widgets/luci_task_attempt_summary_test.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:cocoon_common/build_log_url.dart';
56
import 'package:cocoon_common/task_status.dart';
67
import 'package:flutter/material.dart' hide Key;
78
import 'package:flutter_dashboard/widgets/luci_task_attempt_summary.dart';
@@ -137,10 +138,7 @@ void main() {
137138
await tester.pump();
138139

139140
expect(urlLauncher.launches, isNotEmpty);
140-
expect(
141-
urlLauncher.launches.single,
142-
'${LuciTaskAttemptSummary.luciProdLogBase}/prod/Linux/456',
143-
);
141+
expect(urlLauncher.launches.single, '$luciProdLogBase/prod/Linux/456');
144142
},
145143
);
146144

@@ -173,7 +171,7 @@ void main() {
173171
expect(urlLauncher.launches, isNotEmpty);
174172
expect(
175173
urlLauncher.launches.single,
176-
'${LuciTaskAttemptSummary.dartInternalLogBase}/flutter/Linux%20flutter_release_builder/123',
174+
'$dartInternalLogBase/flutter/Linux%20flutter_release_builder/123',
177175
);
178176
});
179177
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2026 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'is_dart_internal.dart';
6+
7+
/// The base URL for LUCI production logs.
8+
const String luciProdLogBase = 'https://ci.chromium.org/p/flutter/builders';
9+
10+
/// The base URL for dart-internal logs.
11+
const String dartInternalLogBase = 'https://ci.chromium.org/p/dart-internal/builders';
12+
13+
/// Generates a LUCI UI URL for a build log.
14+
String generateBuildLogUrl({
15+
required String buildName,
16+
required int buildNumber,
17+
bool isBringup = false,
18+
}) {
19+
if (isTaskFromDartInternalBuilder(builderName: buildName)) {
20+
return Uri.https('ci.chromium.org', '/p/dart-internal/builders/flutter/$buildName/$buildNumber').toString();
21+
} else {
22+
final pool = isBringup ? 'staging' : 'prod';
23+
return Uri.https('ci.chromium.org', '/p/flutter/builders/$pool/$buildName/$buildNumber').toString();
24+
}
25+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2026 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:cocoon_common/build_log_url.dart';
6+
import 'package:test/test.dart';
7+
8+
void main() {
9+
group('generateBuildLogUrl', () {
10+
test('generates luci prod url', () {
11+
expect(
12+
generateBuildLogUrl(buildName: 'Linux', buildNumber: 123),
13+
'$luciProdLogBase/prod/Linux/123',
14+
);
15+
});
16+
17+
test('generates luci staging url', () {
18+
expect(
19+
generateBuildLogUrl(
20+
buildName: 'Linux',
21+
buildNumber: 123,
22+
isBringup: true,
23+
),
24+
'$luciProdLogBase/staging/Linux/123',
25+
);
26+
});
27+
28+
test('generates dart-internal url', () {
29+
expect(
30+
generateBuildLogUrl(
31+
buildName: 'Linux flutter_release_builder',
32+
buildNumber: 123,
33+
),
34+
'$dartInternalLogBase/flutter/Linux%20flutter_release_builder/123',
35+
);
36+
});
37+
});
38+
}

0 commit comments

Comments
 (0)