blob: 55e01afff17af8d5547ff8f4a457fa3954274314 [file]
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is govered by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
import logging
from google.protobuf import empty_pb2
from api import monorail_servicer
from api import converters
from api.api_proto import issue_objects_pb2
from api.api_proto import issues_pb2
from api.api_proto import issues_prpc_pb2
from businesslogic import work_env
from features import savedqueries_helpers
from framework import exceptions
from framework import framework_views
from proto import tracker_pb2
from search import searchpipeline
from tracker import tracker_bizobj
from tracker import tracker_helpers
class IssuesServicer(monorail_servicer.MonorailServicer):
"""Handle API requests related to Issue objects.
Each API request is implemented with a method as defined in the
.proto file that does any request-specific validation, uses work_env
to safely operate on business objects, and returns a response proto.
"""
DESCRIPTION = issues_prpc_pb2.IssuesServiceDescription
def _GetProjectIssueAndConfig(self, mc, request, use_cache=True):
"""Get three objects that we need for most requests with an issue_ref."""
with work_env.WorkEnv(mc, self.services, phase='getting P, I, C') as we:
project = we.GetProjectByName(
request.issue_ref.project_name, use_cache=use_cache)
mc.LookupLoggedInUserPerms(project)
config = we.GetProjectConfig(project.project_id, use_cache=use_cache)
issue = we.GetIssueByLocalID(
project.project_id, request.issue_ref.local_id, use_cache=use_cache)
return project, issue, config
@monorail_servicer.PRPCMethod
def CreateIssue(self, _mc, request):
response = issue_objects_pb2.Issue()
response.CopyFrom(request.issue)
return response
@monorail_servicer.PRPCMethod
def GetIssue(self, mc, request):
"""Return the specified issue in a response proto."""
project, issue, config = self._GetProjectIssueAndConfig(mc, request)
with work_env.WorkEnv(mc, self.services) as we:
related_refs = we.GetRelatedIssueRefs(issue)
with mc.profiler.Phase('making user views'):
users_involved_in_issue = tracker_bizobj.UsersInvolvedInIssues([issue])
users_by_id = framework_views.MakeAllUserViews(
mc.cnxn, self.services.user, users_involved_in_issue)
framework_views.RevealAllEmailsToMembers(mc.auth, project, users_by_id)
with mc.profiler.Phase('converting to response objects'):
response = issues_pb2.IssueResponse()
response.issue.CopyFrom(converters.ConvertIssue(
issue, users_by_id, related_refs, config))
return response
@monorail_servicer.PRPCMethod
def ListReferencedIssues(self, mc, request):
"""Return the specified issues in a response proto."""
if not request.issue_refs:
return issues_pb2.ListReferencedIssuesResponse()
for issue_ref in request.issue_refs:
if not issue_ref.project_name:
raise exceptions.InputException('Param `project_name` required.')
if not issue_ref.local_id:
raise exceptions.InputException('Param `local_id` required.')
default_project_name = request.issue_refs[0].project_name
ref_tuples = [
(ref.project_name, ref.local_id) for ref in request.issue_refs]
with work_env.WorkEnv(mc, self.services) as we:
open_issues, closed_issues = we.ListReferencedIssues(
ref_tuples, default_project_name)
# TODO(jojwang): monorail:4033, add issue summary, by replacing IssueRef
# protoc objects with Issue protoc objects.
with mc.profiler.Phase('converting to response objects'):
open_refs = [converters.ConvertIssueRef(
(issue.project_name, issue.local_id)) for issue in open_issues]
closed_refs = [converters.ConvertIssueRef(
(issue.project_name, issue.local_id)) for issue in closed_issues]
response = issues_pb2.ListReferencedIssuesResponse(
open_refs=open_refs, closed_refs=closed_refs)
return response
@monorail_servicer.PRPCMethod
def UpdateIssue(self, mc, request):
"""Apply a delta and comment to the specified issue, then return it."""
project, issue, config = self._GetProjectIssueAndConfig(
mc, request, use_cache=False)
with work_env.WorkEnv(mc, self.services) as we:
if request.HasField('delta'):
delta = converters.IngestIssueDelta(
mc.cnxn, self.services, request.delta, config, issue.phases)
else:
delta = tracker_pb2.IssueDelta() # No changes specified.
attachments = converters.IngestAttachmentUploads(request.uploads)
we.UpdateIssue(
issue, delta, request.comment_content, send_email=request.send_email,
attachments=attachments, is_description=request.is_description)
related_refs = we.GetRelatedIssueRefs(issue)
with mc.profiler.Phase('making user views'):
users_involved_in_issue = tracker_bizobj.UsersInvolvedInIssues([issue])
users_by_id = framework_views.MakeAllUserViews(
mc.cnxn, self.services.user, users_involved_in_issue)
framework_views.RevealAllEmailsToMembers(mc.auth, project, users_by_id)
with mc.profiler.Phase('converting to response objects'):
response = issues_pb2.IssueResponse()
response.issue.CopyFrom(converters.ConvertIssue(
issue, users_by_id, related_refs, config))
return response
@monorail_servicer.PRPCMethod
def StarIssue(self, mc, request):
"""Star (or unstar) the specified issue."""
_project, issue, _config = self._GetProjectIssueAndConfig(
mc, request, use_cache=False)
with work_env.WorkEnv(mc, self.services) as we:
we.StarIssue(issue, request.starred)
# Reload the issue to get the new star count.
issue = we.GetIssue(issue.issue_id)
with mc.profiler.Phase('converting to response objects'):
response = issues_pb2.StarIssueResponse()
response.star_count = issue.star_count
return response
@monorail_servicer.PRPCMethod
def IsIssueStarred(self, mc, request):
"""Respond true if the signed-in user has starred the specified issue."""
_project, issue, _config = self._GetProjectIssueAndConfig(
mc, request, use_cache=False)
with work_env.WorkEnv(mc, self.services) as we:
is_starred = we.IsIssueStarred(issue)
with mc.profiler.Phase('converting to response objects'):
response = issues_pb2.IsIssueStarredResponse()
response.is_starred = is_starred
return response
@monorail_servicer.PRPCMethod
def ListComments(self, mc, request):
"""Return comments on the specified issue in a response proto."""
project, issue, config = self._GetProjectIssueAndConfig(mc, request)
with work_env.WorkEnv(mc, self.services) as we:
comments = we.ListIssueComments(issue)
with mc.profiler.Phase('making user views'):
users_involved_in_comments = tracker_bizobj.UsersInvolvedInCommentList(
comments)
users_by_id = framework_views.MakeAllUserViews(
mc.cnxn, self.services.user, users_involved_in_comments)
framework_views.RevealAllEmailsToMembers(mc.auth, project, users_by_id)
with mc.profiler.Phase('converting to response objects'):
converted_comments = converters.ConvertCommentList(
issue, comments, users_by_id, config, mc.auth.user_id)
response = issues_pb2.ListCommentsResponse(comments=converted_comments)
return response
@monorail_servicer.PRPCMethod
def ListActivities(self, mc, request):
"""Return issue activities by a specified user in a response proto."""
converted_user = converters.IngestUserRefs(mc.cnxn,[request.user_ref],
self.services.user)[0]
user = self.services.user.GetUser(mc.cnxn, converted_user)
comments = self.services.issue.GetIssueActivity(
mc.cnxn, user_ids={request.user_ref.user_id})
issues = self.services.issue.GetIssues(
mc.cnxn, {c.issue_id for c in comments})
project_dict = tracker_helpers.GetAllIssueProjects(
mc.cnxn, issues, self.services.project)
config_dict = self.services.config.GetProjectConfigs(
mc.cnxn, project_dict.keys())
allowed_issues = tracker_helpers.FilterOutNonViewableIssues(
mc.auth.effective_ids, user, project_dict,
config_dict, issues)
issue_dict = {issue.issue_id: issue for issue in allowed_issues}
comments = [
c for c in comments if c.issue_id in issue_dict]
users_by_id = framework_views.MakeAllUserViews(
mc.cnxn, self.services.user, [request.user_ref.user_id])
for project in project_dict.values():
framework_views.RevealAllEmailsToMembers(mc.auth, project, users_by_id)
with mc.profiler.Phase('converting to response objects'):
converted_comments = []
for c in comments:
issue = issue_dict.get(c.issue_id)
result = converters.ConvertComment(
issue, c,
users_by_id,
config_dict.get(issue.project_id),
{c.id: 1} if c.is_description else {},
mc.auth.user_id)
converted_comments.append(result)
converted_issues = [issue_objects_pb2.IssueSummary(
project_name=issue.project_name, local_id=issue.local_id,
summary=issue.summary) for issue in allowed_issues]
response = issues_pb2.ListActivitiesResponse(
comments=converted_comments, issue_summaries=converted_issues)
return response
@monorail_servicer.PRPCMethod
def DeleteComment(self, mc, request):
_project, issue, _config = self._GetProjectIssueAndConfig(
mc, request, use_cache=False)
with work_env.WorkEnv(mc, self.services) as we:
all_comments = we.ListIssueComments(issue)
try:
comment = all_comments[request.sequence_num]
except IndexError:
raise exceptions.NoSuchCommentException()
we.DeleteComment(issue, comment, request.delete)
return empty_pb2.Empty()
@monorail_servicer.PRPCMethod
def UpdateApproval(self, mc, request):
"""Update an approval and return the updated approval in a reponse proto."""
project, issue, config = self._GetProjectIssueAndConfig(
mc, request, use_cache=False)
with work_env.WorkEnv(mc, self.services) as we:
approval_fd = tracker_bizobj.FindFieldDef(
request.field_ref.field_name, config)
if not approval_fd:
raise exceptions.NoSuchFieldDefException()
approval_delta = converters.IngestApprovalDelta(
mc.cnxn, self.services.user, request.approval_delta,
mc.auth.user_id, config)
with mc.profiler.Phase('updating approval'):
av, _comment = we.UpdateIssueApproval(
issue.issue_id, approval_fd.field_id, approval_delta,
request.comment_content, request.is_description)
with mc.profiler.Phase('converting to response objects'):
users_by_id = framework_views.MakeAllUserViews(
mc.cnxn, self.services.user, av.approver_ids, [av.setter_id])
framework_views.RevealAllEmailsToMembers(mc.auth, project, users_by_id)
response = issues_pb2.UpdateApprovalResponse()
response.approval.CopyFrom(converters.ConvertApproval(
av, users_by_id, config))
return response
@monorail_servicer.PRPCMethod
def IssueSnapshot(self, mc, request):
"""Fetch IssueSnapshot counts for charting."""
warnings = []
if not request.timestamp:
raise exceptions.InputException('Param `timestamp` required.')
if not request.project_name:
raise exceptions.InputException('Param `project_name` required.')
if request.group_by == 'label' and not request.label_prefix:
raise exceptions.InputException('Param `label_prefix` required.')
if request.canned_query:
canned_query = savedqueries_helpers.SavedQueryIDToCond(
mc.cnxn, self.services.features, request.canned_query)
canned_query, warnings = searchpipeline.ReplaceKeywordsWithUserID(
mc.auth.user_id, canned_query)
else:
canned_query = None
with work_env.WorkEnv(mc, self.services) as we:
project = we.GetProjectByName(request.project_name)
results, unsupported_fields = we.SnapshotCountsQuery(
project, request.timestamp, request.group_by, request.label_prefix,
request.query, canned_query)
snapshot_counts = [
issues_pb2.IssueSnapshotCount(dimension=key, count=result)
for key, result in results.iteritems()
]
response = issues_pb2.IssueSnapshotResponse()
response.snapshot_count.extend(snapshot_counts)
response.unsupported_field.extend(unsupported_fields)
response.unsupported_field.extend(warnings)
return response