Skip to content

Commit

Permalink
Add API api/public/get-status-firestore (flutter#3635)
Browse files Browse the repository at this point in the history
Part of flutter/flutter#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`.
  • Loading branch information
keyonghan authored Apr 15, 2024
1 parent 6987afc commit ab40d73
Show file tree
Hide file tree
Showing 14 changed files with 467 additions and 21 deletions.
1 change: 1 addition & 0 deletions app_dart/lib/cocoon_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
6 changes: 6 additions & 0 deletions app_dart/lib/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ Server createServer({
delegate: GetStatus(config: config),
),

'/api/public/get-status-firestore': CacheRequestHandler<Body>(
cache: cache,
config: config,
delegate: GetStatusFirestore(config: config),
),

'/api/public/get-green-commits': GetGreenCommits(config: config),

/// Record GitHub API quota usage in BigQuery.
Expand Down
24 changes: 24 additions & 0 deletions app_dart/lib/src/model/firestore/commit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -71,6 +81,20 @@ class Commit extends Document {
/// [RepositorySlug] of where this commit exists.
RepositorySlug get slug => RepositorySlug.full(repositoryPath!);

@override
Map<String, dynamic> toJson() {
return <String, dynamic>{
kCommitDocumentName: name,
kCommitRepositoryPath: repositoryPath,
kCommitCreateTimestamp: createTimestamp,
kCommitSha: sha,
kCommitMessage: message,
kCommitAuthor: author,
kCommitAvatar: avatar,
kCommitBranch: branch,
};
}

@override
String toString() {
final StringBuffer buf = StringBuffer()
Expand Down
35 changes: 35 additions & 0 deletions app_dart/lib/src/model/firestore/commit_tasks_status.dart
Original file line number Diff line number Diff line change
@@ -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<Task> tasks;
}

class SerializableCommitTasksStatus {
const SerializableCommitTasksStatus(this.status);

final CommitTasksStatus status;

Map<String, dynamic> toJson() {
return <String, dynamic>{
'Commit': status.commit.toJson(),
'Tasks': status.tasks,
};
}
}
28 changes: 28 additions & 0 deletions app_dart/lib/src/model/firestore/task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -252,6 +264,22 @@ class Task extends Document {
return completedStatuses.contains(status);
}

@override
Map<String, dynamic> toJson() {
return <String, dynamic>{
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()
Expand Down
85 changes: 85 additions & 0 deletions app_dart/lib/src/request_handlers/get_status_firestore.dart
Original file line number Diff line number Diff line change
@@ -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<Body> {
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<Body> 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<SerializableCommitTasksStatus> statuses = await buildStatusService
.retrieveCommitStatusFirestore(
limit: commitNumber,
timestamp: lastCommitTimestamp,
branch: branch,
slug: slug,
)
.map<SerializableCommitTasksStatus>(
(CommitTasksStatus status) => SerializableCommitTasksStatus(status),
)
.toList();

return Body.forJson(<String, dynamic>{
'Statuses': statuses,
});
}

Future<int> _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<String> ownerKey = keyHelper.decode(encodedLastCommitKey) as Key<String>;
final Commit commit = await datastore.db.lookupValue<Commit>(
ownerKey,
orElse: () => throw NotFoundException('Failed to find commit with key $ownerKey'),
);

lastCommitTimestamp = commit.timestamp!;
}
return lastCommitTimestamp;
}
}
18 changes: 1 addition & 17 deletions app_dart/lib/src/service/build_status_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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<Task> tasks;
}

@immutable
class CommitStatus {
/// Creates a new [CommitStatus].
Expand Down
32 changes: 32 additions & 0 deletions app_dart/test/model/firestore/commit_tasks_status_test.dart
Original file line number Diff line number Diff line change
@@ -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, <Task>[]);
expect(SerializableCommitTasksStatus(commitTasksStatus).toJson(), <String, dynamic>{
'Commit': <String, dynamic>{
'DocumentName': 'sha1',
'RepositoryPath': 'flutter/flutter',
'CreateTimestamp': 1,
'Sha': 'sha1',
'Message': 'test message',
'Author': 'author',
'Avatar': 'avatar',
'Branch': 'master',
},
'Tasks': [],
});
});
});
}
15 changes: 15 additions & 0 deletions app_dart/test/model/firestore/commit_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, dynamic> expectedResult = <String, dynamic>{
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);
});
}
17 changes: 17 additions & 0 deletions app_dart/test/model/firestore/task_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,21 @@ void main() {
expect(task.testFlaky, false);
});
});

test('task toJson', () {
final Task taskDocument = generateFirestoreTask(1);
final Map<String, dynamic> expectedResult = <String, dynamic>{
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);
});
}
Loading

0 comments on commit ab40d73

Please sign in to comment.