blob: 1dff24dcffa8aba93e575a11fab09faeec9af684 [file] [log] [blame]
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from google.appengine.ext import ndb
from gae_libs.appengine_util import IsStaging
# Mapping between luci project and monorail project.
_LUCI_PROJECT_TO_MONORAIL_PROJECT = {'chromium': 'chromium'}
class FlakeIssue(ndb.Model):
"""Tracks a Monorail issue for a particular Flake."""
# Project name for Monorail: https://bugs.chromium.org/hosting.
# For example: 'chromium'.
monorail_project = ndb.StringProperty(required=True)
# The issue id.
issue_id = ndb.IntegerProperty(required=True)
# The key to the associated culprit identified by flake analysis, if any.
flake_culprit_key = ndb.KeyProperty(kind='FlakeCulprit')
# Track the last time this issue was created or updated by Flake Detection.
# This field is needed because Flake Detection can only create/update an issue
# at most once every 24 hours.
last_updated_time_by_flake_detection = ndb.DateTimeProperty()
# Key to the FINAL destination of merging chain that this issue is a part of.
# For example, if FlakeIssueA merged into FlakeIssueB (FlakeIssueB is not
# merged into any other issue), the value would be key to FlakeIssueB;
# And if FlakeIssueC is merged into FlakeIssueD, and FlakeIssueD is merged
# into FlakeIssueE, both FlakeIssueC and FlakeIssueD should store the key to
# FlakeIssueE as their merge_destination_key.
# Puts FlakeIssue in quotes because the key refers to the same data model.
merge_destination_key = ndb.KeyProperty(kind='FlakeIssue', indexed=True)
# The most recent updated time the issue was updated in Monorail. Indexed for
# querying by timestamp.
last_updated_time_in_monorail = ndb.DateTimeProperty(indexed=True)
# The status of the issue in monorail, e.g. 'Assigned'. Indexed for querying
# by status.
status = ndb.StringProperty(indexed=True)
# The priority of the bug.
priority = ndb.IntegerProperty(indexed=False)
@staticmethod
def _CreateKey(monorail_project, issue_id): # pragma: no cover
return ndb.Key(FlakeIssue, '%s@%d' % (monorail_project, issue_id))
@staticmethod
def Create(monorail_project, issue_id):
"""Creates a FlakeIssue entity for a Monorail issue."""
return FlakeIssue(
monorail_project=monorail_project,
issue_id=issue_id,
key=FlakeIssue._CreateKey(monorail_project, issue_id))
@staticmethod
def Get(monorail_project, issue_id):
return FlakeIssue._CreateKey(monorail_project, issue_id).get()
@staticmethod
def GetMonorailProjectFromLuciProject(luci_project):
"""Given a luci project, returns the corresponding monorail project.
Args:
luci_project: A luci project name.
Returns:
The corresponding monorail project name if it exists in the mapping,
otherwise None.
"""
return _LUCI_PROJECT_TO_MONORAIL_PROJECT.get(luci_project, None)
@staticmethod
def GetLinkForIssue(monorail_project, issue_id):
"""Given a project and issue id, gets a link to the issue on Monorail.
Args:
monorail_project: Project name of the issue on Monorail.
issue_id: Id of the issue.
Returns:
A link to the issue on Monorail.
"""
assert monorail_project, "A valid project is required."
url_template = 'https://monorail-%s.appspot.com/p/%s/issues/detail?id=%d'
suffix = 'staging' if IsStaging() else 'prod'
return url_template % (suffix, monorail_project, issue_id)
def GetMergeDestination(self):
"""Gets the FlakeIssue entity of this issue's final merged destination."""
return self.merge_destination_key.get(
) if self.merge_destination_key else None
def GetMostUpdatedIssue(self):
return self.GetMergeDestination() or self
def Update(self, **kwargs):
"""Updates arbitrary fields as specified in kwargs."""
any_changes = False
for arg, value in kwargs.iteritems():
current_value = getattr(self, arg, None)
if current_value != value:
setattr(self, arg, value)
any_changes = True
if any_changes:
self.put()