[app_dart] Add BuildPushMessage.fromPushMessage (#2463)
* This is a refactor of existing logic in the pubsubs to the common class
diff --git a/app_dart/lib/src/model/luci/push_message.dart b/app_dart/lib/src/model/luci/push_message.dart
index cc58def..c6c4b95 100644
--- a/app_dart/lib/src/model/luci/push_message.dart
+++ b/app_dart/lib/src/model/luci/push_message.dart
@@ -2,9 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:convert';
+import 'dart:typed_data';
+
import 'package:json_annotation/json_annotation.dart';
import '../../request_handling/body.dart';
+import '../../service/logging.dart';
import '../common/json_converters.dart';
part 'push_message.g.dart';
@@ -82,13 +86,30 @@
}
/// The LUCI build data from a PubSub push message payload.
-@JsonSerializable(includeIfNull: false)
+@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
class BuildPushMessage extends JsonBody {
const BuildPushMessage({
this.build,
this.hostname,
- this.userData,
- });
+ String? userData,
+ }) : rawUserData = userData;
+
+ /// Create [BuildPushMessage] from [PushMessage].
+ factory BuildPushMessage.fromPushMessage(PushMessage message) {
+ final data = message.data;
+ if (data == null) {
+ throw const FormatException('Cannot create BuildPushMessage from null data');
+ }
+
+ try {
+ final String decodedData = String.fromCharCodes(base64.decode(data));
+ log.info('Result message from base64: $decodedData');
+ return BuildPushMessage.fromJson(json.decode(decodedData) as Map<String, dynamic>);
+ } on FormatException {
+ log.info('Result message: $data');
+ return BuildPushMessage.fromJson(json.decode(data) as Map<String, dynamic>);
+ }
+ }
static BuildPushMessage fromJson(Map<String, dynamic> json) => _$BuildPushMessageFromJson(json);
@@ -98,9 +119,32 @@
/// The hostname for the build, e.g. `cr-buildbucket.appspot.com`.
final String? hostname;
- /// User data that was included in the LUCI build request.
+ /// Do not use this value for anything.
+ ///
+ /// This value cannot be marked private due to json_serializable not
+ /// generating on private fields.
+ ///
+ /// This value is used to generate [userData].
@JsonKey(name: 'user_data')
- final String? userData;
+ final String? rawUserData;
+
+ /// User data that was included in the LUCI build request.
+ Map<String, dynamic> get userData {
+ if (rawUserData == null) {
+ return <String, dynamic>{};
+ }
+
+ try {
+ return json.decode(rawUserData!) as Map<String, dynamic>;
+ } on FormatException {
+ final Uint8List bytes = base64.decode(rawUserData!);
+ final String rawJson = String.fromCharCodes(bytes);
+ if (rawJson.isEmpty) {
+ return <String, dynamic>{};
+ }
+ return json.decode(rawJson) as Map<String, dynamic>;
+ }
+ }
@override
Map<String, dynamic> toJson() => _$BuildPushMessageToJson(this);
diff --git a/app_dart/lib/src/model/luci/push_message.g.dart b/app_dart/lib/src/model/luci/push_message.g.dart
index e833e38..2926fea 100644
--- a/app_dart/lib/src/model/luci/push_message.g.dart
+++ b/app_dart/lib/src/model/luci/push_message.g.dart
@@ -81,7 +81,7 @@
writeNotNull('build', instance.build);
writeNotNull('hostname', instance.hostname);
- writeNotNull('user_data', instance.userData);
+ val['user_data'] = instance.userData;
return val;
}
diff --git a/app_dart/lib/src/request_handlers/postsubmit_luci_subscription.dart b/app_dart/lib/src/request_handlers/postsubmit_luci_subscription.dart
index 1d509b3..68f9a76 100644
--- a/app_dart/lib/src/request_handlers/postsubmit_luci_subscription.dart
+++ b/app_dart/lib/src/request_handlers/postsubmit_luci_subscription.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:convert';
-
import 'package:cocoon_service/ci_yaml.dart';
import 'package:gcloud/db.dart';
import 'package:github/github.dart';
@@ -46,42 +44,22 @@
Future<Body> post() async {
final DatastoreService datastore = datastoreProvider(config.db);
- final String data = message.data!;
- BuildPushMessage buildPushMessage;
- try {
- final String decodedData = String.fromCharCodes(base64.decode(data));
- log.info('Result message from base64: $decodedData');
- buildPushMessage = BuildPushMessage.fromJson(json.decode(decodedData) as Map<String, dynamic>);
- } on FormatException {
- log.info('Result message: $data');
- buildPushMessage = BuildPushMessage.fromJson(json.decode(data) as Map<String, dynamic>);
- }
- log.fine(buildPushMessage.userData);
+ final BuildPushMessage buildPushMessage = BuildPushMessage.fromPushMessage(message);
+ log.fine('userData=${buildPushMessage.userData}');
log.fine('Updating buildId=${buildPushMessage.build?.id} for result=${buildPushMessage.build?.result}');
- // Example user data:
- // {
- // "task_key": "key123",
- // }
- if (buildPushMessage.userData == null) {
+ if (buildPushMessage.userData.isEmpty) {
log.fine('User data is empty');
return Body.empty;
}
- Map<String, dynamic> userData;
- try {
- userData = jsonDecode(buildPushMessage.userData!) as Map<String, dynamic>;
- } on FormatException {
- userData = jsonDecode(String.fromCharCodes(base64.decode(buildPushMessage.userData!))) as Map<String, dynamic>;
- }
-
- if (userData.containsKey('repo_owner') && userData.containsKey('repo_name')) {
+ if (buildPushMessage.userData.containsKey('repo_owner') && buildPushMessage.userData.containsKey('repo_name')) {
// Message is coming from a github checks api (postsubmit) enabled repo. We need to
// create the slug from the data in the message and send the check status
// update.
final RepositorySlug slug = RepositorySlug(
- userData['repo_owner'] as String,
- userData['repo_name'] as String,
+ buildPushMessage.userData['repo_owner'] as String,
+ buildPushMessage.userData['repo_name'] as String,
);
await githubChecksService.updateCheckStatus(
buildPushMessage,
@@ -90,8 +68,8 @@
);
}
- final String? rawTaskKey = userData['task_key'] as String?;
- final String? rawCommitKey = userData['commit_key'] as String?;
+ final String? rawTaskKey = buildPushMessage.userData['task_key'] as String?;
+ final String? rawCommitKey = buildPushMessage.userData['commit_key'] as String?;
if (rawCommitKey == null) {
throw const BadRequestException('userData does not contain commit_key');
}
@@ -106,7 +84,7 @@
log.fine('Pulling builder name from parameters_json...');
log.fine(build.buildParameters);
final String? taskName = build.buildParameters?['builder_name'] as String?;
- if (taskName == null) {
+ if (taskName == null || taskName.isEmpty) {
throw const BadRequestException('task_key is null and parameters_json does not contain the builder name');
}
final List<Task> tasks = await datastore.queryRecentTasksByName(name: taskName).toList();
diff --git a/app_dart/lib/src/request_handlers/presubmit_luci_subscription.dart b/app_dart/lib/src/request_handlers/presubmit_luci_subscription.dart
index 8b35a98..f72c357 100644
--- a/app_dart/lib/src/request_handlers/presubmit_luci_subscription.dart
+++ b/app_dart/lib/src/request_handlers/presubmit_luci_subscription.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:convert';
-
import 'package:github/github.dart';
import 'package:meta/meta.dart';
@@ -47,14 +45,7 @@
@override
Future<Body> post() async {
RepositorySlug slug;
- final String data = message.data!;
- BuildPushMessage buildPushMessage;
- try {
- buildPushMessage =
- BuildPushMessage.fromJson(json.decode(String.fromCharCodes(base64.decode(data))) as Map<String, dynamic>);
- } on FormatException {
- buildPushMessage = BuildPushMessage.fromJson(json.decode(data) as Map<String, dynamic>);
- }
+ final BuildPushMessage buildPushMessage = BuildPushMessage.fromPushMessage(message);
final Build build = buildPushMessage.build!;
final String builderName = build.tagsByName('builder').single;
log.fine('Available tags: ${build.tags.toString()}');
@@ -64,21 +55,13 @@
return Body.empty;
}
log.fine('Setting status: ${buildPushMessage.toJson()} for $builderName');
-
- Map<String, dynamic>? userData;
- try {
- userData = jsonDecode(buildPushMessage.userData!) as Map<String, dynamic>;
- } on FormatException {
- userData = jsonDecode(String.fromCharCodes(base64.decode(buildPushMessage.userData!))) as Map<String, dynamic>;
- }
-
- if (userData.containsKey('repo_owner') && userData.containsKey('repo_name')) {
+ if (buildPushMessage.userData.containsKey('repo_owner') && buildPushMessage.userData.containsKey('repo_name')) {
// Message is coming from a github checks api enabled repo. We need to
// create the slug from the data in the message and send the check status
// update.
slug = RepositorySlug(
- userData['repo_owner'] as String,
- userData['repo_name'] as String,
+ buildPushMessage.userData['repo_owner'] as String,
+ buildPushMessage.userData['repo_name'] as String,
);
await githubChecksService.updateCheckStatus(
buildPushMessage,
diff --git a/app_dart/lib/src/service/github_checks_service.dart b/app_dart/lib/src/service/github_checks_service.dart
index a8bf579..1d5ec3a 100644
--- a/app_dart/lib/src/service/github_checks_service.dart
+++ b/app_dart/lib/src/service/github_checks_service.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:convert';
-
import 'package:github/github.dart' as github;
import 'package:github/hooks.dart';
@@ -73,29 +71,23 @@
github.RepositorySlug slug,
) async {
final push_message.Build? build = buildPushMessage.build;
- if (buildPushMessage.userData!.isEmpty) {
+ if (buildPushMessage.userData.isEmpty) {
return false;
}
- Map<String, dynamic> userData;
- try {
- userData = jsonDecode(buildPushMessage.userData!) as Map<String, dynamic>;
- } on FormatException {
- userData = jsonDecode(String.fromCharCodes(base64Decode(buildPushMessage.userData!))) as Map<String, dynamic>;
- }
- if (!userData.containsKey('check_run_id') ||
- !userData.containsKey('repo_owner') ||
- !userData.containsKey('repo_name')) {
+ if (!buildPushMessage.userData.containsKey('check_run_id') ||
+ !buildPushMessage.userData.containsKey('repo_owner') ||
+ !buildPushMessage.userData.containsKey('repo_name')) {
log.severe(
'UserData did not contain check_run_id,'
- 'repo_owner, or repo_name: $userData',
+ 'repo_owner, or repo_name: ${buildPushMessage.userData}',
);
return false;
}
final github.CheckRun checkRun = await githubChecksUtil.getCheckRun(
config,
slug,
- userData['check_run_id'] as int?,
+ buildPushMessage.userData['check_run_id'] as int?,
);
final github.CheckRunStatus status = statusForResult(build!.status);
final github.CheckRunConclusion? conclusion =
diff --git a/app_dart/lib/src/service/luci_build_service.dart b/app_dart/lib/src/service/luci_build_service.dart
index 73b99f8..9179d1f 100644
--- a/app_dart/lib/src/service/luci_build_service.dart
+++ b/app_dart/lib/src/service/luci_build_service.dart
@@ -294,7 +294,6 @@
// V1 bucket name is "luci.flutter.prod" while the api
// is expecting just the last part after "."(prod).
final String bucketName = buildPushMessage.build!.bucket!.split('.').last;
- final Map<String, dynamic>? userData = jsonDecode(buildPushMessage.userData!) as Map<String, dynamic>?;
return await buildBucketClient.scheduleBuild(
ScheduleBuildRequest(
builderId: BuilderId(
@@ -311,7 +310,7 @@
(buildPushMessage.build!.buildParameters!['properties'] as Map<String, dynamic>).cast<String, String>(),
notify: NotificationConfig(
pubsubTopic: 'projects/flutter-dashboard/topics/luci-builds',
- userData: json.encode(userData),
+ userData: json.encode(buildPushMessage.userData),
),
),
);
diff --git a/app_dart/test/request_handlers/postsubmit_luci_subscription_test.dart b/app_dart/test/request_handlers/postsubmit_luci_subscription_test.dart
index 4022e1b..36a7f3c 100644
--- a/app_dart/test/request_handlers/postsubmit_luci_subscription_test.dart
+++ b/app_dart/test/request_handlers/postsubmit_luci_subscription_test.dart
@@ -63,7 +63,8 @@
tester.message = createBuildbucketPushMessage(
'COMPLETED',
result: 'SUCCESS',
- userData: '{}',
+ builderName: '',
+ userData: '{\\"commit_key\\":\\"flutter/main/abc123\\"}',
);
expect(() => tester.post(handler), throwsA(isA<BadRequestException>()));
diff --git a/app_dart/test/src/utilities/mocks.mocks.dart b/app_dart/test/src/utilities/mocks.mocks.dart
index 7f6ba5d..c5d48d5 100644
--- a/app_dart/test/src/utilities/mocks.mocks.dart
+++ b/app_dart/test/src/utilities/mocks.mocks.dart
@@ -2233,7 +2233,7 @@
) as _i17.Future<List<_i10.CheckSuite>>);
@override
_i17.Future<void> updateCheckRun(
- _i3.Config? cocoonConfig,
+ _i3.Config? config,
_i10.RepositorySlug? slug,
_i10.CheckRun? checkRun, {
_i10.CheckRunStatus? status = _i10.CheckRunStatus.queued,
@@ -2245,7 +2245,7 @@
Invocation.method(
#updateCheckRun,
[
- cocoonConfig,
+ config,
slug,
checkRun,
],
@@ -2261,7 +2261,7 @@
) as _i17.Future<void>);
@override
_i17.Future<_i10.CheckRun> getCheckRun(
- _i3.Config? cocoonConfig,
+ _i3.Config? config,
_i10.RepositorySlug? slug,
int? id,
) =>
@@ -2269,7 +2269,7 @@
Invocation.method(
#getCheckRun,
[
- cocoonConfig,
+ config,
slug,
id,
],
@@ -2279,7 +2279,7 @@
Invocation.method(
#getCheckRun,
[
- cocoonConfig,
+ config,
slug,
id,
],
@@ -2288,7 +2288,7 @@
) as _i17.Future<_i10.CheckRun>);
@override
_i17.Future<_i10.CheckRun> createCheckRun(
- _i3.Config? cocoonConfig,
+ _i3.Config? config,
_i10.RepositorySlug? slug,
String? sha,
String? name, {
@@ -2298,7 +2298,7 @@
Invocation.method(
#createCheckRun,
[
- cocoonConfig,
+ config,
slug,
sha,
name,
@@ -2310,7 +2310,7 @@
Invocation.method(
#createCheckRun,
[
- cocoonConfig,
+ config,
slug,
sha,
name,