| // 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:appengine/appengine.dart'; |
| import 'package:collection/collection.dart'; |
| import 'package:gcloud/db.dart'; |
| import 'package:googleapis/bigquery/v2.dart'; |
| import 'package:meta/meta.dart'; |
| |
| import '../datastore/cocoon_config.dart'; |
| import '../foundation/providers.dart'; |
| import '../foundation/typedefs.dart'; |
| import '../model/appengine/agent.dart'; |
| import '../request_handling/api_request_handler.dart'; |
| import '../request_handling/authentication.dart'; |
| import '../request_handling/body.dart'; |
| import '../service/datastore.dart'; |
| |
| const Duration maxHealthCheckAge = Duration(minutes: 10); |
| |
| @immutable |
| class UpdateAgentHealthHistory extends ApiRequestHandler<Body> { |
| const UpdateAgentHealthHistory( |
| Config config, |
| AuthenticationProvider authenticationProvider, { |
| @visibleForTesting |
| this.datastoreProvider = DatastoreService.defaultProvider, |
| @visibleForTesting LoggingProvider loggingProvider, |
| }) : loggingProvider = loggingProvider ?? Providers.serviceScopeLogger, |
| super(config: config, authenticationProvider: authenticationProvider); |
| |
| final DatastoreServiceProvider datastoreProvider; |
| final LoggingProvider loggingProvider; |
| |
| @override |
| Future<Body> get() async { |
| // Define const variables for [BigQuery] operations. |
| const String projectId = 'flutter-dashboard'; |
| const String dataset = 'cocoon'; |
| const String table = 'Agent'; |
| |
| final Logging log = loggingProvider(); |
| final TabledataResourceApi tabledataResourceApi = |
| await config.createTabledataResourceApi(); |
| final DatastoreService datastore = datastoreProvider(config.db); |
| final Query<Agent> agentQuery = datastore.db.query<Agent>() |
| ..order('agentId'); |
| final List<Agent> agents = |
| await agentQuery.run().where(_isVisible).toList(); |
| agents.sort((Agent a, Agent b) => |
| compareAsciiLowerCaseNatural(a.agentId, b.agentId)); |
| |
| final List<Map<String, Object>> tableDataInsertAllRequestRows = |
| <Map<String, Object>>[]; |
| final int healthCheckTimestamp = DateTime.now().millisecondsSinceEpoch; |
| |
| for (Agent agent in agents) { |
| final bool isHealthy = _isAgentHealthy(agent); |
| |
| /// Consolidate [agents] together. |
| /// |
| /// Prepare for bigquery [insertAll]. |
| tableDataInsertAllRequestRows.add(<String, Object>{ |
| 'json': <String, Object>{ |
| 'Timestamp': healthCheckTimestamp, |
| 'AgentID': agent.agentId, |
| // TODO(keyonghan): add more detailed statuses https://github.com/flutter/flutter/issues/44213 |
| 'Status': isHealthy ? 'healthy' : 'unhealthy', |
| 'Detail': isHealthy ? agent.healthDetails : 'out of date', |
| }, |
| }); |
| } |
| |
| /// Prepare final [rows] to be inserted to `BigQuery`. |
| final TableDataInsertAllRequest rows = TableDataInsertAllRequest.fromJson( |
| <String, Object>{'rows': tableDataInsertAllRequestRows}); |
| |
| /// Insert [agents] to `BigQuery`. |
| try { |
| await tabledataResourceApi.insertAll(rows, projectId, dataset, table); |
| log.info( |
| 'Succeeded to insert ${tableDataInsertAllRequestRows.length} rows to $projectId-$dataset-$table'); |
| } on ApiRequestError { |
| log.error('Failed to add commits to BigQuery: $ApiRequestError'); |
| } |
| |
| return Body.forJson(<String, dynamic>{ |
| 'AgentStatuses': agents, |
| }); |
| } |
| |
| bool _isVisible(Agent agent) => !agent.isHidden; |
| |
| bool _isAgentHealthy(Agent agent) { |
| return agent.isHealthy && |
| agent.healthCheckTimestamp != null && |
| DateTime.now().difference(DateTime.fromMillisecondsSinceEpoch( |
| agent.healthCheckTimestamp)) < |
| maxHealthCheckAge; |
| } |
| } |