| Index: dashboard/dashboard/models/alert_group.py
|
| diff --git a/dashboard/dashboard/models/alert_group.py b/dashboard/dashboard/models/alert_group.py
|
| index 8d344f3360f4fae1cda002d81e27cc8f84ef8cbd..55de7a241a98abf90ed9352fa89f09e03cd94d7e 100644
|
| --- a/dashboard/dashboard/models/alert_group.py
|
| +++ b/dashboard/dashboard/models/alert_group.py
|
| @@ -39,13 +39,140 @@ class AlertGroup(ndb.Model):
|
| Args:
|
| grouped_alerts: Alert entities that belong to this group. These
|
| are only given here so that they don't need to be fetched.
|
| + Returns:
|
| + True if modified, False otherwise.
|
| """
|
| min_rev_range = utils.MinimumAlertRange(grouped_alerts)
|
| start, end = min_rev_range if min_rev_range else (None, None)
|
| if self.start_revision != start or self.end_revision != end:
|
| self.start_revision = start
|
| self.end_revision = end
|
| - self.put()
|
| + return True
|
| + return False
|
| +
|
| +
|
| +def ModifyAlertsAndAssociatedGroups(alert_entities, **kwargs):
|
| + """Modifies a list of alerts and their corresponding groups.
|
| +
|
| + There's some book-keeping that needs to be done when modifying an alert,
|
| + specifically when modifying either the bug_id or it's revision range. These
|
| + can potentially trigger modifications or even deletions of AlertGroups.
|
| +
|
| + Args:
|
| + alert_entities: A list of alert entities to modify.
|
| + bug_id: An optional bug_id to set.
|
| + start_revision: An optional start_revision to set.
|
| + end_revision: An optional end_revision to set.
|
| + """
|
| + modified_groups = {}
|
| + modified_alerts = []
|
| + deleted_groups = []
|
| +
|
| + valid_args = ['bug_id', 'start_revision', 'end_revision']
|
| +
|
| + # 1st pass, for each alert that's modified, kick off an async get for
|
| + # it's group.
|
| + group_futures = {}
|
| + valid_alerts = []
|
| + for a in alert_entities:
|
| + if not a.group or a.group.kind() != 'AlertGroup':
|
| + a.group = None
|
| +
|
| + modified = False
|
| +
|
| + # We use kwargs instead of default args since None is actually a valid
|
| + # value to set and using kwargs let's us easily distinguish betwen
|
| + # setting None, and not passing that arg at all.
|
| + for v in valid_args:
|
| + if v in kwargs:
|
| + if getattr(a, v) != kwargs[v]:
|
| + setattr(a, v, kwargs[v])
|
| + modified = True
|
| +
|
| + if not modified:
|
| + continue
|
| +
|
| + modified_alerts.append(a)
|
| +
|
| + if not a.group:
|
| + continue
|
| +
|
| + if not a.group.id() in group_futures:
|
| + group_futures[a.group.id()] = a.group.get_async()
|
| +
|
| + valid_alerts.append(a)
|
| +
|
| + # 2nd pass, for each group, kick off async queries for any other alerts in
|
| + # the same group.
|
| + alert_entities = valid_alerts
|
| + valid_alerts = []
|
| + grouped_alerts_futures = {}
|
| + for a in alert_entities:
|
| + group_future = group_futures[a.group.id()]
|
| + group_entity = group_future.get_result()
|
| + if not group_entity:
|
| + continue
|
| +
|
| + valid_alerts.append(a)
|
| +
|
| + if a.group.id() in grouped_alerts_futures:
|
| + continue
|
| +
|
| + alert_cls = a.__class__
|
| + grouped_alerts_future = alert_cls.query(
|
| + alert_cls.group == group_entity.key).fetch_async()
|
| + grouped_alerts_futures[a.group.id()] = grouped_alerts_future
|
| +
|
| + # 3rd pass, modify groups
|
| + alert_entities = valid_alerts
|
| + grouped_alerts_cache = {}
|
| + for a in alert_entities:
|
| + # We cache these rather than grab get_result() each time because we may
|
| + # modify them in a previous iteration and we want those modifications.
|
| + if a.group.id() in grouped_alerts_cache:
|
| + group_entity, grouped_alerts = grouped_alerts_cache[a.group.id()]
|
| + else:
|
| + group_entity = group_futures[a.group.id()].get_result()
|
| + grouped_alerts = grouped_alerts_futures[a.group.id()].get_result()
|
| + grouped_alerts_cache[a.group.id()] = (group_entity, grouped_alerts)
|
| +
|
| + if not a in grouped_alerts:
|
| + grouped_alerts.append(a)
|
| +
|
| + if 'bug_id' in kwargs:
|
| + bug_id = kwargs['bug_id']
|
| + # The alert has been assigned a real bug ID.
|
| + # Update the group bug ID if necessary.
|
| + if bug_id > 0 and group_entity.bug_id != bug_id:
|
| + group_entity.bug_id = bug_id
|
| + modified_groups[group_entity.key.id()] = group_entity
|
| + # The bug has been marked invalid/ignored. Kick it out of the group.
|
| + elif bug_id < 0 and bug_id is not None:
|
| + a.group = None
|
| + grouped_alerts.remove(a)
|
| + # The bug has been un-triaged. Update the group's bug ID if this is
|
| + # the only alert in the group.
|
| + elif bug_id is None and len(grouped_alerts) == 1:
|
| + group_entity.bug_id = None
|
| + modified_groups[group_entity.key.id()] = group_entity
|
| +
|
| + if group_entity.UpdateRevisionRange(grouped_alerts):
|
| + modified_groups[group_entity.key.id()] = group_entity
|
| +
|
| + # Do final pass to remove all empty groups. If we both delete the group and
|
| + # put() it back after modifications, it's a race as to which actually happens.
|
| + for k, (group_entity, grouped_alerts) in grouped_alerts_cache.iteritems():
|
| + if not grouped_alerts:
|
| + deleted_groups.append(group_entity.key)
|
| + if k in modified_groups:
|
| + del modified_groups[k]
|
| +
|
| + modified_groups = modified_groups.values()
|
| +
|
| + futures = ndb.delete_multi_async(deleted_groups)
|
| + futures.extend(ndb.put_multi_async(modified_alerts + modified_groups))
|
| +
|
| + ndb.Future.wait_all(futures)
|
|
|
|
|
| def GroupAlerts(alerts, test_suite, kind):
|
|
|