Skip to content

Commit

Permalink
[app_dart] Add BuildStatus badge endpoint (#3363)
Browse files Browse the repository at this point in the history
  • Loading branch information
Casey Hillers authored Dec 21, 2023
1 parent 0d406f4 commit d41108d
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 3 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ app_dart/lib/src/request_handlers/dart_internal_subscription.dart @dre
app_dart/lib/src/request_handlers/file_flaky_issue_and_pr.dart @keyonghan
app_dart/lib/src/request_handlers/flush_cache.dart @keyonghan
app_dart/lib/src/request_handlers/get_build_status.dart @keyonghan
app_dart/lib/src/request_handlers/get_build_status.dart @CaseyHillers
app_dart/lib/src/request_handlers/get_release_branches.dart @CaseyHillers
app_dart/lib/src/request_handlers/get_repos.dart @keyonghan
app_dart/lib/src/request_handlers/get_status.dart @keyonghan
Expand Down
6 changes: 6 additions & 0 deletions app_dart/bin/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ Future<void> main() async {
delegate: GetBuildStatus(config: config),
ttl: const Duration(seconds: 15),
),
'/api/public/build-status-badge': CacheRequestHandler<Body>(
cache: cache,
config: config,
delegate: GetBuildStatusBadge(config: config),
ttl: const Duration(seconds: 15),
),
'/api/public/get-release-branches': CacheRequestHandler<Body>(
cache: cache,
config: config,
Expand Down
1 change: 1 addition & 0 deletions app_dart/lib/cocoon_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export 'src/request_handlers/dart_internal_subscription.dart';
export 'src/request_handlers/file_flaky_issue_and_pr.dart';
export 'src/request_handlers/flush_cache.dart';
export 'src/request_handlers/get_build_status.dart';
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';
Expand Down
9 changes: 6 additions & 3 deletions app_dart/lib/src/request_handlers/get_build_status.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ class GetBuildStatus extends RequestHandler<Body> {

@override
Future<Body> get() async {
final BuildStatusResponse response = await createResponse();
return Body.forJson(response.writeToJsonMap());
}

Future<BuildStatusResponse> createResponse() async {
final DatastoreService datastore = datastoreProvider(config.db);
final BuildStatusService buildStatusService = buildStatusProvider(datastore);
final String repoName = request!.uri.queryParameters[kRepoParam] ?? 'flutter';
final RepositorySlug slug = RepositorySlug('flutter', repoName);
final BuildStatus status = (await buildStatusService.calculateCumulativeStatus(slug))!;
final BuildStatusResponse response = BuildStatusResponse()
return BuildStatusResponse()
..buildStatus = status.succeeded ? EnumBuildStatus.success : EnumBuildStatus.failure
..failingTasks.addAll(status.failedTasks);

return Body.forJson(response.writeToJsonMap());
}
}
63 changes: 63 additions & 0 deletions app_dart/lib/src/request_handlers/get_build_status_badge.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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 'dart:io';

import 'package:cocoon_service/protos.dart';
import 'package:meta/meta.dart';

import '../request_handling/body.dart';
import 'get_build_status.dart';

/// [GetBuildStatusBadge] returns an SVG representing the current tree status for the given repo.
///
/// It reuses [GetBuildStatus] and translates it to the SVG. The primary caller for this is the
/// README's from the larger Flutter repositories.
@immutable
class GetBuildStatusBadge extends GetBuildStatus {
const GetBuildStatusBadge({
required super.config,
@visibleForTesting super.datastoreProvider,
@visibleForTesting super.buildStatusProvider,
});

/// Provides a template that is easily injectable.
///
/// Template follows the mustache format of `{{ VARIABLE }}`.
final String template =
'''<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="150" height="20" role="img" aria-label="Flutter CI: {{ STATUS }}">
<title>Flutter CI: {{ STATUS }}</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<clipPath id="r"><rect width="238" height="20" rx="3" fill="#fff"/></clipPath>
<g clip-path="url(#r)">
<rect width="80" height="20" fill="#555"/>
<rect x="80" width="107" height="20" fill="{{ COLOR }}"/>
<rect width="238" height="20" fill="url(#s)"/></g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
<text x="380" y="140" transform="scale(.1)" fill="#fff" textLength="600">Flutter CI</text>
<text x="1125" y="140" transform="scale(.1)" fill="#fff" textLength="550">{{ STATUS }}</text></g>
</svg>''';

static const red = '#e05d44';
static const green = '#3BB143';

@override
Future<Body> get() async {
// Set HTTP content-type so SVG is viewable.
final HttpResponse response = request!.response;
response.headers.contentType = ContentType.parse('image/svg+xml');
final BuildStatusResponse buildStatusResponse = await super.createResponse();
return Body.forString(generateSVG(buildStatusResponse));
}

String generateSVG(BuildStatusResponse response) {
final bool passing = response.failingTasks.isEmpty;
return template
.replaceAll('{{ STATUS }}', passing ? 'passing' : '${response.failingTasks.length} failures')
.replaceAll('{{ COLOR }}', passing ? green : red);
}
}
29 changes: 29 additions & 0 deletions app_dart/test/request_handlers/get_build_status_badge_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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/protos.dart';
import 'package:cocoon_service/src/request_handlers/get_build_status_badge.dart';
import 'package:test/test.dart';

import '../src/datastore/fake_config.dart';

void main() {
final GetBuildStatusBadge handler = GetBuildStatusBadge(config: FakeConfig());

test('passing status', () async {
final BuildStatusResponse buildStatusResponse = BuildStatusResponse()..buildStatus = EnumBuildStatus.success;
final String response = handler.generateSVG(buildStatusResponse);
expect(response, contains('Flutter CI: passing'));
expect(response, contains(GetBuildStatusBadge.green));
});

test('failing status', () async {
final BuildStatusResponse buildStatusResponse = BuildStatusResponse()
..buildStatus = EnumBuildStatus.failure
..failingTasks.addAll(<String>['a', 'b', 'c']); // 3 failing tasks
final String response = handler.generateSVG(buildStatusResponse);
expect(response, contains('Flutter CI: 3 failures'));
expect(response, contains(GetBuildStatusBadge.red));
});
}

0 comments on commit d41108d

Please sign in to comment.