From ab40d730c346e861f720505f46e0987f16aa1f75 Mon Sep 17 00:00:00 2001 From: keyonghan <54558023+keyonghan@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:09:57 -0700 Subject: [PATCH] Add API `api/public/get-status-firestore` (#3635) Part of https://github.com/flutter/flutter/issues/142951. This PR adds api entry point for `api/public/get-status-firestore`. This is prepare frontend migration to Firestore. This change is a no-op, as the frontend is still calling `api/public/get-status`. --- app_dart/lib/cocoon_service.dart | 1 + app_dart/lib/server.dart | 6 + app_dart/lib/src/model/firestore/commit.dart | 24 ++ .../model/firestore/commit_tasks_status.dart | 35 +++ app_dart/lib/src/model/firestore/task.dart | 28 +++ .../get_status_firestore.dart | 85 +++++++ .../src/service/build_status_provider.dart | 18 +- .../firestore/commit_tasks_status_test.dart | 32 +++ .../test/model/firestore/commit_test.dart | 15 ++ app_dart/test/model/firestore/task_test.dart | 17 ++ .../get_status_firestore_test.dart | 209 ++++++++++++++++++ .../service/fake_build_status_provider.dart | 8 +- .../test/src/utilities/entity_generators.dart | 6 + dashboard/lib/service/appengine_cocoon.dart | 4 +- 14 files changed, 467 insertions(+), 21 deletions(-) create mode 100644 app_dart/lib/src/model/firestore/commit_tasks_status.dart create mode 100644 app_dart/lib/src/request_handlers/get_status_firestore.dart create mode 100644 app_dart/test/model/firestore/commit_tasks_status_test.dart create mode 100644 app_dart/test/request_handlers/get_status_firestore_test.dart diff --git a/app_dart/lib/cocoon_service.dart b/app_dart/lib/cocoon_service.dart index b8b3dac2f..f050db2e2 100644 --- a/app_dart/lib/cocoon_service.dart +++ b/app_dart/lib/cocoon_service.dart @@ -14,6 +14,7 @@ export 'src/request_handlers/get_build_status_badge.dart'; export 'src/request_handlers/get_release_branches.dart'; export 'src/request_handlers/get_repos.dart'; export 'src/request_handlers/get_status.dart'; +export 'src/request_handlers/get_status_firestore.dart'; export 'src/request_handlers/get_green_commits.dart'; export 'src/request_handlers/github_rate_limit_status.dart'; export 'src/request_handlers/github_webhook.dart'; diff --git a/app_dart/lib/server.dart b/app_dart/lib/server.dart index 5aa02cfb7..540a3e44b 100644 --- a/app_dart/lib/server.dart +++ b/app_dart/lib/server.dart @@ -231,6 +231,12 @@ Server createServer({ delegate: GetStatus(config: config), ), + '/api/public/get-status-firestore': CacheRequestHandler( + cache: cache, + config: config, + delegate: GetStatusFirestore(config: config), + ), + '/api/public/get-green-commits': GetGreenCommits(config: config), /// Record GitHub API quota usage in BigQuery. diff --git a/app_dart/lib/src/model/firestore/commit.dart b/app_dart/lib/src/model/firestore/commit.dart index a8411d0e5..ccf119a61 100644 --- a/app_dart/lib/src/model/firestore/commit.dart +++ b/app_dart/lib/src/model/firestore/commit.dart @@ -18,6 +18,16 @@ const String kCommitMessageField = 'message'; const String kCommitRepositoryPathField = 'repositoryPath'; const String kCommitShaField = 'sha'; +/// Commit Json keys. +const String kCommitAvatar = 'Avatar'; +const String kCommitAuthor = 'Author'; +const String kCommitBranch = 'Branch'; +const String kCommitCreateTimestamp = 'CreateTimestamp'; +const String kCommitDocumentName = 'DocumentName'; +const String kCommitMessage = 'Message'; +const String kCommitRepositoryPath = 'RepositoryPath'; +const String kCommitSha = 'Sha'; + class Commit extends Document { /// Lookup [Commit] from Firestore. /// @@ -71,6 +81,20 @@ class Commit extends Document { /// [RepositorySlug] of where this commit exists. RepositorySlug get slug => RepositorySlug.full(repositoryPath!); + @override + Map toJson() { + return { + kCommitDocumentName: name, + kCommitRepositoryPath: repositoryPath, + kCommitCreateTimestamp: createTimestamp, + kCommitSha: sha, + kCommitMessage: message, + kCommitAuthor: author, + kCommitAvatar: avatar, + kCommitBranch: branch, + }; + } + @override String toString() { final StringBuffer buf = StringBuffer() diff --git a/app_dart/lib/src/model/firestore/commit_tasks_status.dart b/app_dart/lib/src/model/firestore/commit_tasks_status.dart new file mode 100644 index 000000000..c5bc651d9 --- /dev/null +++ b/app_dart/lib/src/model/firestore/commit_tasks_status.dart @@ -0,0 +1,35 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'commit.dart'; +import 'task.dart'; + +/// Class that holds the status for all tasks corresponding to a particular +/// commit. +/// +/// Tasks may still be running, and thus their status is subject to change. +/// Put another way, this class holds information that is a snapshot in time. +class CommitTasksStatus { + /// Creates a new [CommitTasksStatus]. + const CommitTasksStatus(this.commit, this.tasks); + + /// The commit against which all the tasks are run. + final Commit commit; + + /// Tasks running against the commit. + final List tasks; +} + +class SerializableCommitTasksStatus { + const SerializableCommitTasksStatus(this.status); + + final CommitTasksStatus status; + + Map toJson() { + return { + 'Commit': status.commit.toJson(), + 'Tasks': status.tasks, + }; + } +} diff --git a/app_dart/lib/src/model/firestore/task.dart b/app_dart/lib/src/model/firestore/task.dart index b0abc6d0e..8af818b60 100644 --- a/app_dart/lib/src/model/firestore/task.dart +++ b/app_dart/lib/src/model/firestore/task.dart @@ -26,6 +26,18 @@ const String kTaskStartTimestampField = 'startTimestamp'; const String kTaskStatusField = 'status'; const String kTaskTestFlakyField = 'testFlaky'; +/// Task Json keys. +const String kTaskAttempts = 'Attempts'; +const String kTaskBringup = 'Bringup'; +const String kTaskBuildNumber = 'BuildNumber'; +const String kTaskCreateTimestamp = 'CreateTimestamp'; +const String kTaskDocumentName = 'DocumentName'; +const String kTaskEndTimestamp = 'EndTimestamp'; +const String kTaskStartTimestamp = 'StartTimestamp'; +const String kTaskStatus = 'Status'; +const String kTaskTaskName = 'TaskName'; +const String kTaskTestFlaky = 'TestFlaky'; + class Task extends Document { /// Lookup [Task] from Firestore. /// @@ -252,6 +264,22 @@ class Task extends Document { return completedStatuses.contains(status); } + @override + Map toJson() { + return { + kTaskDocumentName: name, + kTaskCreateTimestamp: createTimestamp, + kTaskStartTimestamp: startTimestamp, + kTaskEndTimestamp: endTimestamp, + kTaskTaskName: taskName, + kTaskAttempts: attempts, + kTaskBringup: bringup, + kTaskTestFlaky: testFlaky, + kTaskBuildNumber: buildNumber, + kTaskStatus: status, + }; + } + @override String toString() { final StringBuffer buf = StringBuffer() diff --git a/app_dart/lib/src/request_handlers/get_status_firestore.dart b/app_dart/lib/src/request_handlers/get_status_firestore.dart new file mode 100644 index 000000000..1565f0369 --- /dev/null +++ b/app_dart/lib/src/request_handlers/get_status_firestore.dart @@ -0,0 +1,85 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:gcloud/db.dart'; +import 'package:github/github.dart'; +import 'package:meta/meta.dart'; + +import '../model/appengine/commit.dart'; +import '../model/firestore/commit_tasks_status.dart'; +import '../model/appengine/key_helper.dart'; +import '../request_handling/body.dart'; +import '../request_handling/exceptions.dart'; +import '../request_handling/request_handler.dart'; +import '../service/build_status_provider.dart'; +import '../service/config.dart'; +import '../service/datastore.dart'; +import '../service/firestore.dart'; + +@immutable +class GetStatusFirestore extends RequestHandler { + const GetStatusFirestore({ + required super.config, + @visibleForTesting this.datastoreProvider = DatastoreService.defaultProvider, + @visibleForTesting this.buildStatusProvider = BuildStatusService.defaultProvider, + }); + + final DatastoreServiceProvider datastoreProvider; + final BuildStatusServiceProvider buildStatusProvider; + + static const String kLastCommitKeyParam = 'lastCommitKey'; + static const String kBranchParam = 'branch'; + static const String kRepoParam = 'repo'; + + @override + Future get() async { + final String? encodedLastCommitKey = request!.uri.queryParameters[kLastCommitKeyParam]; + final String repoName = request!.uri.queryParameters[kRepoParam] ?? Config.flutterSlug.name; + final RepositorySlug slug = RepositorySlug('flutter', repoName); + final String branch = request!.uri.queryParameters[kBranchParam] ?? Config.defaultBranch(slug); + final DatastoreService datastore = datastoreProvider(config.db); + final FirestoreService firestoreService = await config.createFirestoreService(); + final BuildStatusService buildStatusService = buildStatusProvider(datastore, firestoreService); + final KeyHelper keyHelper = config.keyHelper; + final int commitNumber = config.commitNumber; + final int lastCommitTimestamp = await _obtainTimestamp(encodedLastCommitKey, keyHelper, datastore); + + final List statuses = await buildStatusService + .retrieveCommitStatusFirestore( + limit: commitNumber, + timestamp: lastCommitTimestamp, + branch: branch, + slug: slug, + ) + .map( + (CommitTasksStatus status) => SerializableCommitTasksStatus(status), + ) + .toList(); + + return Body.forJson({ + 'Statuses': statuses, + }); + } + + Future _obtainTimestamp(String? encodedLastCommitKey, KeyHelper keyHelper, DatastoreService datastore) async { + /// [lastCommitTimestamp] is initially set as the current time, which allows query return + /// latest commit list. If [owerKeyParam] is not empty, [lastCommitTimestamp] will be set + /// as the timestamp of that [commit], and the query will return lastest commit + /// list whose timestamp is smaller than [lastCommitTimestamp]. + int lastCommitTimestamp = DateTime.now().millisecondsSinceEpoch; + + if (encodedLastCommitKey != null) { + final Key ownerKey = keyHelper.decode(encodedLastCommitKey) as Key; + final Commit commit = await datastore.db.lookupValue( + ownerKey, + orElse: () => throw NotFoundException('Failed to find commit with key $ownerKey'), + ); + + lastCommitTimestamp = commit.timestamp!; + } + return lastCommitTimestamp; + } +} diff --git a/app_dart/lib/src/service/build_status_provider.dart b/app_dart/lib/src/service/build_status_provider.dart index cc5439d33..b4c4a4575 100644 --- a/app_dart/lib/src/service/build_status_provider.dart +++ b/app_dart/lib/src/service/build_status_provider.dart @@ -12,6 +12,7 @@ import '../model/appengine/commit.dart' as datastore; import '../model/appengine/github_build_status_update.dart'; import '../model/appengine/stage.dart'; import '../model/firestore/commit.dart'; +import '../model/firestore/commit_tasks_status.dart'; import '../model/firestore/task.dart'; import 'datastore.dart'; @@ -174,23 +175,6 @@ class BuildStatusService { } } -/// Class that holds the status for all tasks corresponding to a particular -/// commit. -/// -/// Tasks may still be running, and thus their status is subject to change. -/// Put another way, this class holds information that is a snapshot in time. -@immutable -class CommitTasksStatus { - /// Creates a new [CommitTasksStatus]. - const CommitTasksStatus(this.commit, this.tasks); - - /// The commit against which all the tasks are run. - final Commit commit; - - /// Tasks running against the commit. - final List tasks; -} - @immutable class CommitStatus { /// Creates a new [CommitStatus]. diff --git a/app_dart/test/model/firestore/commit_tasks_status_test.dart b/app_dart/test/model/firestore/commit_tasks_status_test.dart new file mode 100644 index 000000000..e72d8065a --- /dev/null +++ b/app_dart/test/model/firestore/commit_tasks_status_test.dart @@ -0,0 +1,32 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:cocoon_service/src/model/firestore/commit.dart'; +import 'package:cocoon_service/src/model/firestore/commit_tasks_status.dart'; +import 'package:cocoon_service/src/model/firestore/task.dart'; +import 'package:test/test.dart'; + +import '../../src/utilities/entity_generators.dart'; + +void main() { + group('CommitTasksStatus', () { + test('generates json correctly', () async { + final Commit commit = generateFirestoreCommit(1, sha: 'sha1'); + final CommitTasksStatus commitTasksStatus = CommitTasksStatus(commit, []); + expect(SerializableCommitTasksStatus(commitTasksStatus).toJson(), { + 'Commit': { + 'DocumentName': 'sha1', + 'RepositoryPath': 'flutter/flutter', + 'CreateTimestamp': 1, + 'Sha': 'sha1', + 'Message': 'test message', + 'Author': 'author', + 'Avatar': 'avatar', + 'Branch': 'master', + }, + 'Tasks': [], + }); + }); + }); +} diff --git a/app_dart/test/model/firestore/commit_test.dart b/app_dart/test/model/firestore/commit_test.dart index 2734b54f2..5cf5730c2 100644 --- a/app_dart/test/model/firestore/commit_test.dart +++ b/app_dart/test/model/firestore/commit_test.dart @@ -51,4 +51,19 @@ void main() { expect(commitDocument.fields![kCommitRepositoryPathField]!.stringValue, commit.repository); expect(commitDocument.fields![kCommitShaField]!.stringValue, commit.sha); }); + + test('commit toJson', () { + final Commit commitDocument = generateFirestoreCommit(1); + final Map expectedResult = { + kCommitDocumentName: commitDocument.name, + kCommitRepositoryPath: commitDocument.repositoryPath, + kCommitCreateTimestamp: commitDocument.createTimestamp, + kCommitSha: commitDocument.sha, + kCommitMessage: commitDocument.message, + kCommitAuthor: commitDocument.author, + kCommitAvatar: commitDocument.avatar, + kCommitBranch: commitDocument.branch, + }; + expect(commitDocument.toJson(), expectedResult); + }); } diff --git a/app_dart/test/model/firestore/task_test.dart b/app_dart/test/model/firestore/task_test.dart index ab748e3bf..b4c778dde 100644 --- a/app_dart/test/model/firestore/task_test.dart +++ b/app_dart/test/model/firestore/task_test.dart @@ -202,4 +202,21 @@ void main() { expect(task.testFlaky, false); }); }); + + test('task toJson', () { + final Task taskDocument = generateFirestoreTask(1); + final Map expectedResult = { + kTaskDocumentName: taskDocument.name, + kTaskCreateTimestamp: taskDocument.createTimestamp, + kTaskStartTimestamp: taskDocument.startTimestamp, + kTaskEndTimestamp: taskDocument.endTimestamp, + kTaskTaskName: taskDocument.taskName, + kTaskAttempts: taskDocument.attempts, + kTaskBringup: taskDocument.bringup, + kTaskTestFlaky: taskDocument.testFlaky, + kTaskBuildNumber: taskDocument.buildNumber, + kTaskStatus: taskDocument.status, + }; + expect(taskDocument.toJson(), expectedResult); + }); } diff --git a/app_dart/test/request_handlers/get_status_firestore_test.dart b/app_dart/test/request_handlers/get_status_firestore_test.dart new file mode 100644 index 000000000..b8bcef0d1 --- /dev/null +++ b/app_dart/test/request_handlers/get_status_firestore_test.dart @@ -0,0 +1,209 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// 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/cocoon_service.dart'; +import 'package:cocoon_service/src/model/appengine/commit.dart'; +import 'package:cocoon_service/src/model/firestore/commit_tasks_status.dart'; +import 'package:cocoon_service/src/model/firestore/task.dart'; +import 'package:cocoon_service/src/service/datastore.dart'; +import 'package:gcloud/db.dart'; +import 'package:test/test.dart'; + +import '../src/datastore/fake_config.dart'; +import '../src/request_handling/fake_authentication.dart'; +import '../src/request_handling/fake_http.dart'; +import '../src/request_handling/request_handler_tester.dart'; +import '../src/service/fake_build_status_provider.dart'; +import '../src/utilities/entity_generators.dart'; +import '../src/utilities/mocks.dart'; + +void main() { + group('GetStatusFirestore', () { + late FakeConfig config; + FakeClientContext clientContext; + FakeKeyHelper keyHelper; + FakeBuildStatusService buildStatusService; + late RequestHandlerTester tester; + late GetStatusFirestore handler; + late MockFirestoreService mockFirestoreService; + + late Commit commit1; + late Commit commit2; + + Future decodeHandlerBody() async { + final Body body = await tester.get(handler); + return await utf8.decoder.bind(body.serialize() as Stream>).transform(json.decoder).single as T?; + } + + setUp(() { + clientContext = FakeClientContext(); + mockFirestoreService = MockFirestoreService(); + keyHelper = FakeKeyHelper(applicationContext: clientContext.applicationContext); + tester = RequestHandlerTester(); + config = FakeConfig(keyHelperValue: keyHelper, firestoreService: mockFirestoreService); + buildStatusService = FakeBuildStatusService(commitTasksStatuses: []); + handler = GetStatusFirestore( + config: config, + datastoreProvider: (DatastoreDB db) => DatastoreService(config.db, 5), + buildStatusProvider: (_, __) => buildStatusService, + ); + commit1 = Commit( + key: config.db.emptyKey.append(Commit, id: 'flutter/flutter/ea28a9c34dc701de891eaf74503ca4717019f829'), + repository: 'flutter/flutter', + sha: 'ea28a9c34dc701de891eaf74503ca4717019f829', + timestamp: 3, + message: 'test message 1', + branch: 'master', + ); + commit2 = Commit( + key: config.db.emptyKey.append(Commit, id: 'flutter/flutter/d5b0b3c8d1c5fd89302089077ccabbcfaae045e4'), + repository: 'flutter/flutter', + sha: 'd5b0b3c8d1c5fd89302089077ccabbcfaae045e4', + timestamp: 1, + message: 'test message 2', + branch: 'master', + ); + }); + + test('no statuses', () async { + final Map result = (await decodeHandlerBody())!; + expect(result['Statuses'], isEmpty); + }); + + test('reports statuses without input commit key', () async { + buildStatusService = FakeBuildStatusService( + commitTasksStatuses: [ + CommitTasksStatus(generateFirestoreCommit(1), const []), + CommitTasksStatus(generateFirestoreCommit(2), const []), + ], + ); + handler = GetStatusFirestore( + config: config, + datastoreProvider: (DatastoreDB db) => DatastoreService(config.db, 5), + buildStatusProvider: (_, __) => buildStatusService, + ); + + final Map result = (await decodeHandlerBody())!; + + expect(result['Statuses'].length, 2); + }); + + test('reports statuses with input commit key', () async { + final Commit commit1 = Commit( + key: config.db.emptyKey.append(Commit, id: 'flutter/flutter/ea28a9c34dc701de891eaf74503ca4717019f829'), + repository: 'flutter/flutter', + sha: 'ea28a9c34dc701de891eaf74503ca4717019f829', + timestamp: 3, + message: 'test message 1', + branch: 'master', + ); + final Commit commit2 = Commit( + key: config.db.emptyKey.append(Commit, id: 'flutter/flutter/d5b0b3c8d1c5fd89302089077ccabbcfaae045e4'), + repository: 'flutter/flutter', + sha: 'd5b0b3c8d1c5fd89302089077ccabbcfaae045e4', + timestamp: 1, + message: 'test message 2', + branch: 'master', + ); + config.db.values[commit1.key] = commit1; + config.db.values[commit2.key] = commit2; + buildStatusService = FakeBuildStatusService( + commitTasksStatuses: [ + CommitTasksStatus( + generateFirestoreCommit( + 1, + sha: 'd5b0b3c8d1c5fd89302089077ccabbcfaae045e4', + ), + const [], + ), + CommitTasksStatus(generateFirestoreCommit(2), const []), + ], + ); + handler = GetStatusFirestore( + config: config, + datastoreProvider: (DatastoreDB db) => DatastoreService(config.db, 5), + buildStatusProvider: (_, __) => buildStatusService, + ); + + const String expectedLastCommitKeyEncoded = + 'ahNzfmZsdXR0ZXItZGFzaGJvYXJkckcLEglDaGVja2xpc3QiOGZsdXR0ZXIvZmx1dHRlci9lYTI4YTljMzRkYzcwMWRlODkxZWFmNzQ1MDNjYTQ3MTcwMTlmODI5DA'; + + tester.request = FakeHttpRequest( + queryParametersValue: { + GetStatusFirestore.kLastCommitKeyParam: expectedLastCommitKeyEncoded, + }, + ); + final Map result = (await decodeHandlerBody())!; + + expect(result['Statuses'].first, { + 'Commit': { + 'DocumentName': 'd5b0b3c8d1c5fd89302089077ccabbcfaae045e4', + 'RepositoryPath': 'flutter/flutter', + 'CreateTimestamp': 1, + 'Sha': 'd5b0b3c8d1c5fd89302089077ccabbcfaae045e4', + 'Message': 'test message', + 'Author': 'author', + 'Avatar': 'avatar', + 'Branch': 'master', + }, + 'Tasks': [], + }); + }); + + test('reports statuses with input branch', () async { + commit2.branch = 'flutter-1.1-candidate.1'; + config.db.values[commit1.key] = commit1; + config.db.values[commit2.key] = commit2; + buildStatusService = FakeBuildStatusService( + commitTasksStatuses: [ + CommitTasksStatus( + generateFirestoreCommit(1), + const [], + ), + CommitTasksStatus( + generateFirestoreCommit( + 2, + branch: 'flutter-1.1-candidate.1', + sha: 'd5b0b3c8d1c5fd89302089077ccabbcfaae045e4', + ), + const [], + ), + ], + ); + handler = GetStatusFirestore( + config: config, + datastoreProvider: (DatastoreDB db) => DatastoreService(config.db, 5), + buildStatusProvider: (_, __) => buildStatusService, + ); + + const String branch = 'flutter-1.1-candidate.1'; + + expect(config.db.values.length, 2); + + tester.request = FakeHttpRequest( + queryParametersValue: { + GetStatusFirestore.kBranchParam: branch, + }, + ); + final Map result = (await decodeHandlerBody())!; + + expect(result['Statuses'].length, 1); + expect(result['Statuses'].first, { + 'Commit': { + 'DocumentName': 'd5b0b3c8d1c5fd89302089077ccabbcfaae045e4', + 'RepositoryPath': 'flutter/flutter', + 'CreateTimestamp': 2, + 'Sha': 'd5b0b3c8d1c5fd89302089077ccabbcfaae045e4', + 'Message': 'test message', + 'Author': 'author', + 'Avatar': 'avatar', + 'Branch': 'flutter-1.1-candidate.1', + }, + 'Tasks': [], + }); + }); + }); +} diff --git a/app_dart/test/src/service/fake_build_status_provider.dart b/app_dart/test/src/service/fake_build_status_provider.dart index dfe8a201f..37ef6a190 100644 --- a/app_dart/test/src/service/fake_build_status_provider.dart +++ b/app_dart/test/src/service/fake_build_status_provider.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:cocoon_service/src/model/firestore/commit_tasks_status.dart'; import 'package:cocoon_service/src/service/build_status_provider.dart'; import 'package:cocoon_service/src/service/datastore.dart'; import 'package:cocoon_service/src/service/firestore.dart'; @@ -11,6 +12,7 @@ class FakeBuildStatusService implements BuildStatusService { FakeBuildStatusService({ this.cumulativeStatus, this.commitStatuses, + this.commitTasksStatuses, }); BuildStatus? cumulativeStatus; @@ -55,10 +57,12 @@ class FakeBuildStatusService implements BuildStatusService { String? branch, required RepositorySlug slug, }) { - if (commitStatuses == null) { + if (commitTasksStatuses == null) { throw AssertionError(); } - commitStatuses!.sort((CommitStatus a, CommitStatus b) => a.commit.timestamp!.compareTo(b.commit.timestamp!)); + commitTasksStatuses!.sort( + (CommitTasksStatus a, CommitTasksStatus b) => a.commit.createTimestamp!.compareTo(b.commit.createTimestamp!), + ); return Stream.fromIterable( commitTasksStatuses!.where( diff --git a/app_dart/test/src/utilities/entity_generators.dart b/app_dart/test/src/utilities/entity_generators.dart index 1be5d9bd8..7b9eab83a 100644 --- a/app_dart/test/src/utilities/entity_generators.dart +++ b/app_dart/test/src/utilities/entity_generators.dart @@ -139,6 +139,9 @@ firestore_commit.Commit generateFirestoreCommit( String? owner = 'flutter', String repo = 'flutter', int? createTimestamp, + String? message = 'test message', + String? author = 'author', + String? avatar = 'avatar', }) { final firestore_commit.Commit commit = firestore_commit.Commit() ..name = sha ?? '$i' @@ -146,6 +149,9 @@ firestore_commit.Commit generateFirestoreCommit( firestore_commit.kCommitCreateTimestampField: Value(integerValue: (createTimestamp ?? i).toString()), firestore_commit.kCommitRepositoryPathField: Value(stringValue: '$owner/$repo'), firestore_commit.kCommitBranchField: Value(stringValue: branch), + firestore_commit.kCommitMessageField: Value(stringValue: message), + firestore_commit.kCommitAuthorField: Value(stringValue: author), + firestore_commit.kCommitAvatarField: Value(stringValue: avatar), firestore_commit.kCommitShaField: Value(stringValue: sha ?? '$i'), }; return commit; diff --git a/dashboard/lib/service/appengine_cocoon.dart b/dashboard/lib/service/appengine_cocoon.dart index 2119846df..1189b980f 100644 --- a/dashboard/lib/service/appengine_cocoon.dart +++ b/dashboard/lib/service/appengine_cocoon.dart @@ -56,7 +56,7 @@ class AppEngineCocoonService implements CocoonService { static const String kTaskEndTimestamp = 'EndTimestamp'; static const String kTaskStartTimestamp = 'StartTimestamp'; static const String kTaskStatus = 'Status'; - static const String kTaskTaskNmae = 'TaskName'; + static const String kTaskTaskName = 'TaskName'; static const String kTaskTestFlaky = 'TestFlaky'; final http.Client _client; @@ -407,7 +407,7 @@ class AppEngineCocoonService implements CocoonService { ..startTimestamp = Int64(taskData[kTaskStartTimestamp] as int) ..endTimestamp = Int64(taskData[kTaskEndTimestamp] as int) ..documentName = taskData[kTaskDocumentName] as String - ..taskName = taskData[kTaskTaskNmae] as String + ..taskName = taskData[kTaskTaskName] as String ..attempts = taskData[kTaskAttempts] as int ..bringup = taskData[kTaskBringup] as bool ..status = taskData[kTaskStatus] as String