14a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Copyright 2015 The Chromium Authors. All rights reserved. 24a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Use of this source code is governed by a BSD-style license that can be 34a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# found in the LICENSE file. 44a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 54a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair"""Provides the web interface for displaying an overview of alerts.""" 64a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 74a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport json 8972bd9a9d2c6597a0145a675cbfa527d0510b048Martijn Coenenimport logging 94a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom google.appengine.ext import ndb 114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom dashboard import email_template 134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom dashboard import request_handler 144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom dashboard import utils 154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom dashboard.models import anomaly 164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom dashboard.models import sheriff 174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom dashboard.models import stoppage_alert 184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair_MAX_ANOMALIES_TO_COUNT = 5000 204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair_MAX_ANOMALIES_TO_SHOW = 500 214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair_MAX_STOPPAGE_ALERTS = 500 224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass AlertsHandler(request_handler.RequestHandler): 254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Shows an overview of recent anomalies for perf sheriffing.""" 264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def get(self): 28cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Renders the UI for listing alerts.""" 29cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.RenderStaticHtml('alerts.html') 30cef7893435aa41160dd1255c43cb8498279738ccChris Craik 31cef7893435aa41160dd1255c43cb8498279738ccChris Craik def post(self): 32cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Returns dynamic data for listing alerts in response to XHR. 334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Request parameters: 354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair sheriff: The name of a sheriff (optional). 364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair triaged: Whether to include triaged alerts (i.e. with a bug ID). 374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair improvements: Whether to include improvement anomalies. 384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Outputs: 40cef7893435aa41160dd1255c43cb8498279738ccChris Craik JSON data for an XHR request to show a table of alerts. 414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair sheriff_name = self.request.get('sheriff', 'Chromium Perf Sheriff') 434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair sheriff_key = ndb.Key('Sheriff', sheriff_name) 44cef7893435aa41160dd1255c43cb8498279738ccChris Craik if not _SheriffIsFound(sheriff_key): 45cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.response.out.write(json.dumps({ 46cef7893435aa41160dd1255c43cb8498279738ccChris Craik 'error': 'Sheriff "%s" not found.' % sheriff_name 47cef7893435aa41160dd1255c43cb8498279738ccChris Craik })) 48cef7893435aa41160dd1255c43cb8498279738ccChris Craik return 49cef7893435aa41160dd1255c43cb8498279738ccChris Craik 504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair include_improvements = bool(self.request.get('improvements')) 514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair include_triaged = bool(self.request.get('triaged')) 524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair anomaly_keys = _FetchAnomalyKeys( 544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair sheriff_key, include_improvements, include_triaged) 554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair anomalies = ndb.get_multi(anomaly_keys[:_MAX_ANOMALIES_TO_SHOW]) 564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair stoppage_alerts = _FetchStoppageAlerts(sheriff_key, include_triaged) 574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 58cef7893435aa41160dd1255c43cb8498279738ccChris Craik values = { 59cef7893435aa41160dd1255c43cb8498279738ccChris Craik 'anomaly_list': AnomalyDicts(anomalies), 60cef7893435aa41160dd1255c43cb8498279738ccChris Craik 'stoppage_alert_list': StoppageAlertDicts(stoppage_alerts), 61cef7893435aa41160dd1255c43cb8498279738ccChris Craik 'sheriff_list': _GetSheriffList(), 62cef7893435aa41160dd1255c43cb8498279738ccChris Craik } 63cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.GetDynamicVariables(values) 64cef7893435aa41160dd1255c43cb8498279738ccChris Craik self.response.out.write(json.dumps(values)) 65cef7893435aa41160dd1255c43cb8498279738ccChris Craik 66cef7893435aa41160dd1255c43cb8498279738ccChris Craik 67cef7893435aa41160dd1255c43cb8498279738ccChris Craikdef _SheriffIsFound(sheriff_key): 68cef7893435aa41160dd1255c43cb8498279738ccChris Craik """Checks whether the sheriff can be found for the current user.""" 69cef7893435aa41160dd1255c43cb8498279738ccChris Craik try: 70cef7893435aa41160dd1255c43cb8498279738ccChris Craik sheriff_entity = sheriff_key.get() 71cef7893435aa41160dd1255c43cb8498279738ccChris Craik except AssertionError: 72cef7893435aa41160dd1255c43cb8498279738ccChris Craik # This assertion is raised in InternalOnlyModel._post_get_hook, 73cef7893435aa41160dd1255c43cb8498279738ccChris Craik # and indicates an internal-only Sheriff but an external user. 74cef7893435aa41160dd1255c43cb8498279738ccChris Craik return False 75cef7893435aa41160dd1255c43cb8498279738ccChris Craik return sheriff_entity is not None 764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef _FetchAnomalyKeys(sheriff_key, include_improvements, include_triaged): 794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Fetches the list of Anomaly keys that may be shown. 804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Args: 824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair sheriff_key: The ndb.Key for the Sheriff to fetch alerts for. 834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair include_improvements: Whether to include improvement Anomalies. 844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair include_triaged: Whether to include Anomalies with a bug ID already set. 854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Returns: 874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair A list of Anomaly keys, in reverse-chronological order. 884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair query = anomaly.Anomaly.query( 904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair anomaly.Anomaly.sheriff == sheriff_key) 914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if not include_improvements: 934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair query = query.filter( 944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair anomaly.Anomaly.is_improvement == False) 954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if not include_triaged: 974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair query = query.filter( 984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair anomaly.Anomaly.bug_id == None) 994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair query = query.filter( 1004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair anomaly.Anomaly.recovered == False) 1014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair query = query.order(-anomaly.Anomaly.timestamp) 1034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return query.fetch(limit=_MAX_ANOMALIES_TO_COUNT, keys_only=True) 1044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef _FetchStoppageAlerts(sheriff_key, include_triaged): 1074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Fetches the list of Anomaly keys that may be shown. 1084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Args: 1104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair sheriff_key: The ndb.Key for the Sheriff to fetch alerts for. 1114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair include_triaged: Whether to include alerts with a bug ID. 1124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Returns: 1144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair A list of StoppageAlert entities, in reverse-chronological order. 1154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 1164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair query = stoppage_alert.StoppageAlert.query( 1174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair stoppage_alert.StoppageAlert.sheriff == sheriff_key) 1184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if not include_triaged: 1204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair query = query.filter( 1214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair stoppage_alert.StoppageAlert.bug_id == None) 1224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair query = query.filter( 1234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair stoppage_alert.StoppageAlert.recovered == False) 1244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair query = query.order(-stoppage_alert.StoppageAlert.timestamp) 1264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return query.fetch(limit=_MAX_STOPPAGE_ALERTS) 1274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef _GetSheriffList(): 1304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Returns a list of sheriff names for all sheriffs in the datastore.""" 1314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair sheriff_keys = sheriff.Sheriff.query().fetch(keys_only=True) 1324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return [key.string_id() for key in sheriff_keys] 1334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef AnomalyDicts(anomalies): 1364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Makes a list of dicts with properties of Anomaly entities.""" 1374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair bisect_statuses = _GetBisectStatusDict(anomalies) 1384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return [GetAnomalyDict(a, bisect_statuses.get(a.bug_id)) for a in anomalies] 1394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef StoppageAlertDicts(stoppage_alerts): 1424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Makes a list of dicts with properties of StoppageAlert entities.""" 1434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return [_GetStoppageAlertDict(a) for a in stoppage_alerts] 1444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef GetAnomalyDict(anomaly_entity, bisect_status=None): 1474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Returns a dictionary for an Anomaly which can be encoded as JSON. 1484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Args: 1504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair anomaly_entity: An Anomaly entity. 1514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair bisect_status: String status of bisect run. 1524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Returns: 1544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair A dictionary which is safe to be encoded as JSON. 1554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 1564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair alert_dict = _AlertDict(anomaly_entity) 1574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair alert_dict.update({ 1584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'median_after_anomaly': anomaly_entity.median_after_anomaly, 1594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'median_before_anomaly': anomaly_entity.median_before_anomaly, 1604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'percent_changed': '%s' % anomaly_entity.GetDisplayPercentChanged(), 1614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'improvement': anomaly_entity.is_improvement, 1624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'bisect_status': bisect_status, 1634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'recovered': anomaly_entity.recovered, 1644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair }) 1654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return alert_dict 1664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef _GetStoppageAlertDict(stoppage_alert_entity): 1694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Returns a dictionary of properties of a stoppage alert.""" 1704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair alert_dict = _AlertDict(stoppage_alert_entity) 171972bd9a9d2c6597a0145a675cbfa527d0510b048Martijn Coenen last_row_date = stoppage_alert_entity.last_row_date 172972bd9a9d2c6597a0145a675cbfa527d0510b048Martijn Coenen if not last_row_date: 173972bd9a9d2c6597a0145a675cbfa527d0510b048Martijn Coenen logging.error('No date for StoppageAlert:\n%s', stoppage_alert_entity) 1744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair alert_dict.update({ 1754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'mail_sent': stoppage_alert_entity.mail_sent, 176972bd9a9d2c6597a0145a675cbfa527d0510b048Martijn Coenen 'last_row_date': str(last_row_date.date()) if last_row_date else 'N/A', 1774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'recovered': stoppage_alert_entity.recovered, 1784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair }) 1794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return alert_dict 1804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef _AlertDict(alert_entity): 1834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Returns a base dictionary with properties common to all alerts.""" 1844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair test_path = utils.TestPath(alert_entity.test) 1854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair test_path_parts = test_path.split('/') 1864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair dashboard_link = email_template.GetReportPageLink( 1874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair test_path, rev=alert_entity.end_revision, add_protocol_and_host=False) 1884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return { 1894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'key': alert_entity.key.urlsafe(), 1904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'group': alert_entity.group.urlsafe() if alert_entity.group else None, 1914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'start_revision': alert_entity.start_revision, 1924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'end_revision': alert_entity.end_revision, 1934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'date': str(alert_entity.timestamp.date()), 1944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'master': test_path_parts[0], 1954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'bot': test_path_parts[1], 1964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'testsuite': test_path_parts[2], 1974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'test': '/'.join(test_path_parts[3:]), 1984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'bug_id': alert_entity.bug_id, 1994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'dashboard_link': dashboard_link, 2004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair } 2014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef _GetBisectStatusDict(anomalies): 2044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Returns a dictionary of bug ID to bisect status string.""" 2054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair bug_id_list = {a.bug_id for a in anomalies if a.bug_id > 0} 2064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair bugs = ndb.get_multi(ndb.Key('Bug', b) for b in bug_id_list) 2074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return {b.key.id(): b.latest_bisect_status for b in bugs if b} 208