Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/issues_servicer.py b/api/issues_servicer.py
new file mode 100644
index 0000000..1cdfeca
--- /dev/null
+++ b/api/issues_servicer.py
@@ -0,0 +1,801 @@
+# 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 or at
+# https://developers.google.com/open-source/licenses/bsd
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import copy
+import logging
+
+from google.protobuf import empty_pb2
+
+import settings
+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 filterrules_helpers
+from features import savedqueries_helpers
+from framework import exceptions
+from framework import framework_constants
+from framework import framework_views
+from framework import permissions
+from proto import tracker_pb2
+from search import searchpipeline
+from tracker import field_helpers
+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, issue_ref, use_cache=True, issue_required=True,
+ view_deleted=False):
+ """Get three objects that we need for most requests with an issue_ref."""
+ issue = None
+ with work_env.WorkEnv(mc, self.services, phase='getting P, I, C') as we:
+ project = we.GetProjectByName(
+ issue_ref.project_name, use_cache=use_cache)
+ mc.LookupLoggedInUserPerms(project)
+ config = we.GetProjectConfig(project.project_id, use_cache=use_cache)
+ if issue_required or issue_ref.local_id:
+ try:
+ issue = we.GetIssueByLocalID(
+ project.project_id, issue_ref.local_id, use_cache=use_cache,
+ allow_viewing_deleted=view_deleted)
+ except exceptions.NoSuchIssueException as e:
+ issue = None
+ if issue_required:
+ raise e
+ return project, issue, config
+
+ def _GetProjectIssueIDsAndConfig(
+ self, mc, issue_refs, use_cache=True):
+ """Get info from a single project for repeated issue_refs requests."""
+ project_names = set()
+ local_ids = []
+ for issue_ref in issue_refs:
+ if not issue_ref.local_id:
+ raise exceptions.InputException('Param `local_id` required.')
+ local_ids.append(issue_ref.local_id)
+ if issue_ref.project_name:
+ project_names.add(issue_ref.project_name)
+
+ if not project_names:
+ raise exceptions.InputException('Param `project_name` required.')
+ if len(project_names) != 1:
+ raise exceptions.InputException(
+ 'This method does not support cross-project issue_refs.')
+ project_name = project_names.pop()
+ with work_env.WorkEnv(mc, self.services, phase='getting P, I ids, C') as we:
+ project = we.GetProjectByName(project_name, use_cache=use_cache)
+ mc.LookupLoggedInUserPerms(project)
+ config = we.GetProjectConfig(project.project_id, use_cache=use_cache)
+ project_local_id_pairs = [(project.project_id, local_id)
+ for local_id in local_ids]
+ issue_ids, _misses = self.services.issue.LookupIssueIDs(
+ mc.cnxn, project_local_id_pairs)
+ return project, issue_ids, 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."""
+ issue_ref = request.issue_ref
+ project, issue, config = self._GetProjectIssueAndConfig(
+ mc, issue_ref, view_deleted=True, issue_required=False)
+
+ # Code for getting where a moved issue was moved to.
+ if issue is None:
+ moved_to_ref = self.services.issue.GetCurrentLocationOfMovedIssue(
+ mc.cnxn, project.project_id, issue_ref.local_id)
+ moved_to_project_id, moved_to_id = moved_to_ref
+ moved_to_project_name = None
+
+ if moved_to_project_id is not None:
+ with work_env.WorkEnv(mc, self.services) as we:
+ moved_to_project = we.GetProject(moved_to_project_id)
+ moved_to_project_name = moved_to_project.project_name
+ return issues_pb2.IssueResponse(moved_to_ref=converters.ConvertIssueRef(
+ (moved_to_project_name, moved_to_id)))
+
+ raise exceptions.NoSuchIssueException()
+
+ if issue.deleted:
+ return issues_pb2.IssueResponse(
+ issue=issue_objects_pb2.Issue(is_deleted=True))
+
+ 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.cnxn, self.services, mc.auth, users_by_id, project)
+
+ 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 ListIssues(self, mc, request):
+ """Return the list of issues for projects that satisfy the given query."""
+ use_cached_searches = not settings.local_mode
+ can = request.canned_query or 1
+ with work_env.WorkEnv(mc, self.services) as we:
+ start, max_items = converters.IngestPagination(request.pagination)
+ pipeline = we.ListIssues(
+ request.query, request.project_names, mc.auth.user_id, max_items,
+ start, can, request.group_by_spec, request.sort_spec,
+ use_cached_searches)
+ with mc.profiler.Phase('reveal emails to members'):
+ projects = self.services.project.GetProjectsByName(
+ mc.cnxn, request.project_names)
+ for _, p in projects.items():
+ framework_views.RevealAllEmailsToMembers(
+ mc.cnxn, self.services, mc.auth, pipeline.users_by_id, p)
+
+ converted_results = []
+ with work_env.WorkEnv(mc, self.services) as we:
+ for issue in (pipeline.visible_results or []):
+ related_refs = we.GetRelatedIssueRefs([issue])
+ converted_results.append(
+ converters.ConvertIssue(issue, pipeline.users_by_id, related_refs,
+ pipeline.harmonized_config))
+ total_results = 0
+ if hasattr(pipeline.pagination, 'total_count'):
+ total_results = pipeline.pagination.total_count
+ return issues_pb2.ListIssuesResponse(
+ issues=converted_results, total_results=total_results)
+
+
+ @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)
+ all_issues = open_issues + closed_issues
+ all_project_ids = [issue.project_id for issue in all_issues]
+ related_refs = we.GetRelatedIssueRefs(all_issues)
+ configs = we.GetProjectConfigs(all_project_ids)
+
+ with mc.profiler.Phase('making user views'):
+ users_involved = tracker_bizobj.UsersInvolvedInIssues(all_issues)
+ users_by_id = framework_views.MakeAllUserViews(
+ mc.cnxn, self.services.user, users_involved)
+ framework_views.RevealAllEmailsToMembers(
+ mc.cnxn, self.services, mc.auth, users_by_id)
+
+ with mc.profiler.Phase('converting to response objects'):
+ converted_open_issues = [
+ converters.ConvertIssue(
+ issue, users_by_id, related_refs, configs[issue.project_id])
+ for issue in open_issues]
+ converted_closed_issues = [
+ converters.ConvertIssue(
+ issue, users_by_id, related_refs, configs[issue.project_id])
+ for issue in closed_issues]
+ response = issues_pb2.ListReferencedIssuesResponse(
+ open_refs=converted_open_issues, closed_refs=converted_closed_issues)
+
+ return response
+
+ @monorail_servicer.PRPCMethod
+ def ListApplicableFieldDefs(self, mc, request):
+ """Returns specified issues' applicable field refs in a response proto."""
+ if not request.issue_refs:
+ return issues_pb2.ListApplicableFieldDefsResponse()
+
+ _project, issue_ids, config = self._GetProjectIssueIDsAndConfig(
+ mc, request.issue_refs)
+ with work_env.WorkEnv(mc, self.services) as we:
+ issues_dict = we.GetIssuesDict(issue_ids)
+ fds = field_helpers.ListApplicableFieldDefs(issues_dict.values(), config)
+
+ users_by_id = {}
+ with mc.profiler.Phase('converting to response objects'):
+ users_involved = tracker_bizobj.UsersInvolvedInConfig(config)
+ users_by_id.update(framework_views.MakeAllUserViews(
+ mc.cnxn, self.services.user, users_involved))
+ field_defs = [
+ converters.ConvertFieldDef(fd, [], users_by_id, config, True)
+ for fd in fds]
+
+ return issues_pb2.ListApplicableFieldDefsResponse(field_defs=field_defs)
+
+ @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.issue_ref, 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,
+ kept_attachments=list(request.kept_attachments))
+ 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.cnxn, self.services, mc.auth, users_by_id, project)
+
+ 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.issue_ref, 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.issue_ref, 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 ListStarredIssues(self, mc, _request):
+ """Return a list of issue ids that the signed-in user has starred."""
+ with work_env.WorkEnv(mc, self.services) as we:
+ starred_issues = we.ListStarredIssueIDs()
+ starred_issues_dict = we.GetIssueRefs(starred_issues)
+
+ with mc.profiler.Phase('converting to response objects'):
+ converted_starred_issue_refs = converters.ConvertIssueRefs(
+ starred_issues, starred_issues_dict)
+ response = issues_pb2.ListStarredIssuesResponse(
+ starred_issue_refs=converted_starred_issue_refs)
+
+ 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.issue_ref)
+ with work_env.WorkEnv(mc, self.services) as we:
+ comments = we.ListIssueComments(issue)
+ _, comment_reporters = we.LookupIssueFlaggers(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.cnxn, self.services, mc.auth, users_by_id, project)
+
+ with mc.profiler.Phase('converting to response objects'):
+ issue_perms = permissions.UpdateIssuePermissions(
+ mc.perms, project, issue, mc.auth.effective_ids, config=config)
+ converted_comments = converters.ConvertCommentList(
+ issue, comments, config, users_by_id, comment_reporters,
+ mc.auth.user_id, issue_perms)
+ 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.IngestUserRef(mc.cnxn, request.user_ref,
+ self.services.user)
+ 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, list(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],
+ tracker_bizobj.UsersInvolvedInCommentList(comments))
+ for project in project_dict.values():
+ framework_views.RevealAllEmailsToMembers(
+ mc.cnxn, self.services, mc.auth, users_by_id, project)
+
+ issues_by_project = {}
+ for issue in allowed_issues:
+ issues_by_project.setdefault(issue.project_id, []).append(issue)
+
+ # A dictionary {issue_id: perms} of the PermissionSet for the current user
+ # on each of the issues.
+ issue_perms_dict = {}
+ # A dictionary {comment_id: [reporter_id]} of users who have reported the
+ # comment as spam.
+ comment_reporters = {}
+ for project_id, project_issues in issues_by_project.items():
+ mc.LookupLoggedInUserPerms(project_dict[project_id])
+ issue_perms_dict.update({
+ issue.issue_id: permissions.UpdateIssuePermissions(
+ mc.perms, project_dict[issue.project_id], issue,
+ mc.auth.effective_ids, config=config_dict[issue.project_id])
+ for issue in project_issues})
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ project_issue_reporters = we.LookupIssuesFlaggers(project_issues)
+ for _, issue_comment_reporters in project_issue_reporters.values():
+ comment_reporters.update(issue_comment_reporters)
+
+ with mc.profiler.Phase('converting to response objects'):
+ converted_comments = []
+ for c in comments:
+ issue = issue_dict.get(c.issue_id)
+ issue_perms = issue_perms_dict.get(c.issue_id)
+ result = converters.ConvertComment(
+ issue, c,
+ config_dict.get(issue.project_id),
+ users_by_id,
+ comment_reporters.get(c.id, []),
+ {c.id: 1} if c.is_description else {},
+ mc.auth.user_id, issue_perms)
+ 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.issue_ref, 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 BulkUpdateApprovals(self, mc, request):
+ """Update multiple issues' approval and return the updated issue_refs."""
+ if not request.issue_refs:
+ raise exceptions.InputException('Param `issue_refs` empty.')
+
+ project, issue_ids, config = self._GetProjectIssueIDsAndConfig(
+ mc, request.issue_refs)
+
+ approval_fd = tracker_bizobj.FindFieldDef(
+ request.field_ref.field_name, config)
+ if not approval_fd:
+ raise exceptions.NoSuchFieldDefException()
+ if request.HasField('approval_delta'):
+ approval_delta = converters.IngestApprovalDelta(
+ mc.cnxn, self.services.user, request.approval_delta,
+ mc.auth.user_id, config)
+ else:
+ approval_delta = tracker_pb2.ApprovalDelta()
+ # No bulk adding approval attachments for now.
+
+ with work_env.WorkEnv(mc, self.services, phase='updating approvals') as we:
+ updated_issue_ids = we.BulkUpdateIssueApprovals(
+ issue_ids, approval_fd.field_id, project, approval_delta,
+ request.comment_content, send_email=request.send_email)
+ with mc.profiler.Phase('converting to response objects'):
+ issue_ref_pairs = we.GetIssueRefs(updated_issue_ids)
+ issue_refs = [converters.ConvertIssueRef(pair)
+ for pair in issue_ref_pairs.values()]
+ response = issues_pb2.BulkUpdateApprovalsResponse(issue_refs=issue_refs)
+
+ return response
+
+ @monorail_servicer.PRPCMethod
+ def UpdateApproval(self, mc, request):
+ """Update and return an approval in a response proto."""
+ project, issue, config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, use_cache=False)
+
+ approval_fd = tracker_bizobj.FindFieldDef(
+ request.field_ref.field_name, config)
+ if not approval_fd:
+ raise exceptions.NoSuchFieldDefException()
+ if request.HasField('approval_delta'):
+ approval_delta = converters.IngestApprovalDelta(
+ mc.cnxn, self.services.user, request.approval_delta,
+ mc.auth.user_id, config)
+ else:
+ approval_delta = tracker_pb2.ApprovalDelta()
+ attachments = converters.IngestAttachmentUploads(request.uploads)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ av, _comment, _issue = we.UpdateIssueApproval(
+ issue.issue_id,
+ approval_fd.field_id,
+ approval_delta,
+ request.comment_content,
+ request.is_description,
+ attachments=attachments,
+ send_email=request.send_email,
+ kept_attachments=list(request.kept_attachments))
+
+ 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.cnxn, self.services, mc.auth, users_by_id, project)
+ response = issues_pb2.UpdateApprovalResponse()
+ response.approval.CopyFrom(converters.ConvertApproval(
+ av, users_by_id, config))
+
+ return response
+
+ @monorail_servicer.PRPCMethod
+ def ConvertIssueApprovalsTemplate(self, mc, request):
+ """Update an issue's existing approvals structure to match the one of the
+ given template."""
+
+ if not request.issue_ref.local_id or not request.issue_ref.project_name:
+ raise exceptions.InputException('Param `issue_ref.local_id` empty')
+ if not request.template_name:
+ raise exceptions.InputException('Param `template_name` empty')
+
+ project, issue, config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, use_cache=False)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ we.ConvertIssueApprovalsTemplate(
+ config, issue, request.template_name, request.comment_content,
+ send_email=request.send_email)
+ 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.cnxn, self.services, mc.auth, users_by_id, project)
+
+ with mc.profiler.Phase('converting to response objects'):
+ response = issues_pb2.ConvertIssueApprovalsTemplateResponse()
+ response.issue.CopyFrom(converters.ConvertIssue(
+ issue, users_by_id, related_refs, 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 and not request.hotlist_id:
+ raise exceptions.InputException('Params `project_name` or `hotlist_id` '
+ '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)
+ # TODO(jrobbins): support linked accounts me_user_ids.
+ canned_query, warnings = searchpipeline.ReplaceKeywordsWithUserIDs(
+ [mc.auth.user_id], canned_query)
+ else:
+ canned_query = None
+
+ if request.query:
+ query, warnings = searchpipeline.ReplaceKeywordsWithUserIDs(
+ [mc.auth.user_id], request.query)
+ else:
+ query = None
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ try:
+ project = we.GetProjectByName(request.project_name)
+ except exceptions.NoSuchProjectException:
+ project = None
+
+ if request.hotlist_id:
+ hotlist = we.GetHotlist(request.hotlist_id)
+ else:
+ hotlist = None
+
+ results, unsupported_fields, limit_reached = we.SnapshotCountsQuery(
+ project, request.timestamp, request.group_by,
+ label_prefix=request.label_prefix, query=query,
+ canned_query=canned_query, hotlist=hotlist)
+ if request.group_by == 'owner':
+ # Map user ids to emails.
+ snapshot_counts = [
+ issues_pb2.IssueSnapshotCount(
+ dimension=self.services.user.GetUser(mc.cnxn, key).email,
+ count=result) for key, result in results.iteritems()
+ ]
+ else:
+ snapshot_counts = [
+ issues_pb2.IssueSnapshotCount(dimension=key, count=result)
+ for key, result in results.items()
+ ]
+ response = issues_pb2.IssueSnapshotResponse()
+ response.snapshot_count.extend(snapshot_counts)
+ response.unsupported_field.extend(unsupported_fields)
+ response.unsupported_field.extend(warnings)
+ response.search_limit_reached = limit_reached
+ return response
+
+ @monorail_servicer.PRPCMethod
+ def PresubmitIssue(self, mc, request):
+ """Provide the UI with warnings and suggestions."""
+ project, issue, config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, issue_required=False)
+
+ with mc.profiler.Phase('making user views'):
+ try:
+ proposed_owner_id = converters.IngestUserRef(
+ mc.cnxn, request.issue_delta.owner_ref, self.services.user)
+ except exceptions.NoSuchUserException:
+ proposed_owner_id = 0
+
+ users_by_id = framework_views.MakeAllUserViews(
+ mc.cnxn, self.services.user, [proposed_owner_id])
+ proposed_owner_view = users_by_id[proposed_owner_id]
+
+ with mc.profiler.Phase('Applying IssueDelta'):
+ if issue:
+ proposed_issue = copy.deepcopy(issue)
+ else:
+ proposed_issue = tracker_pb2.Issue(
+ owner_id=framework_constants.NO_USER_SPECIFIED,
+ project_id=config.project_id)
+ issue_delta = converters.IngestIssueDelta(
+ mc.cnxn, self.services, request.issue_delta, config, None,
+ ignore_missing_objects=True)
+ tracker_bizobj.ApplyIssueDelta(
+ mc.cnxn, self.services.issue, proposed_issue, issue_delta, config)
+
+ with mc.profiler.Phase('applying rules'):
+ _, traces = filterrules_helpers.ApplyFilterRules(
+ mc.cnxn, self.services, proposed_issue, config)
+ logging.info('proposed_issue is now: %r', proposed_issue)
+ logging.info('traces are: %r', traces)
+
+ with mc.profiler.Phase('making derived user views'):
+ derived_users_by_id = framework_views.MakeAllUserViews(
+ mc.cnxn, self.services.user, [proposed_issue.derived_owner_id],
+ proposed_issue.derived_cc_ids)
+ framework_views.RevealAllEmailsToMembers(
+ mc.cnxn, self.services, mc.auth, derived_users_by_id, project)
+
+ with mc.profiler.Phase('pair derived values with rule explanations'):
+ (derived_labels, derived_owners, derived_ccs, warnings, errors) = (
+ tracker_helpers.PairDerivedValuesWithRuleExplanations(
+ proposed_issue, traces, derived_users_by_id))
+
+ result = issues_pb2.PresubmitIssueResponse(
+ owner_availability=proposed_owner_view.avail_message_short,
+ owner_availability_state=proposed_owner_view.avail_state,
+ derived_labels=converters.ConvertValueAndWhyList(derived_labels),
+ derived_owners=converters.ConvertValueAndWhyList(derived_owners),
+ derived_ccs=converters.ConvertValueAndWhyList(derived_ccs),
+ warnings=converters.ConvertValueAndWhyList(warnings),
+ errors=converters.ConvertValueAndWhyList(errors))
+ return result
+
+ @monorail_servicer.PRPCMethod
+ def RerankBlockedOnIssues(self, mc, request):
+ """Rerank the blocked on issues for the given issue ref."""
+ moved_issue_id, target_issue_id = converters.IngestIssueRefs(
+ mc.cnxn, [request.moved_ref, request.target_ref], self.services)
+ _project, issue, _config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ we.RerankBlockedOnIssues(
+ issue, moved_issue_id, target_issue_id, request.split_above)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ issue = we.GetIssue(issue.issue_id)
+ related_refs = we.GetRelatedIssueRefs([issue])
+
+ with mc.profiler.Phase('converting to response objects'):
+ converted_issue_refs = converters.ConvertIssueRefs(
+ issue.blocked_on_iids, related_refs)
+ result = issues_pb2.RerankBlockedOnIssuesResponse(
+ blocked_on_issue_refs=converted_issue_refs)
+
+ return result
+
+ @monorail_servicer.PRPCMethod
+ def DeleteIssue(self, mc, request):
+ """Mark or unmark the given issue as deleted."""
+ _project, issue, _config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, view_deleted=True)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ we.DeleteIssue(issue, request.delete)
+
+ result = issues_pb2.DeleteIssueResponse()
+ return result
+
+ @monorail_servicer.PRPCMethod
+ def DeleteIssueComment(self, mc, request):
+ """Mark or unmark the given comment as deleted."""
+ _project, issue, _config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, use_cache=False)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ comments = we.ListIssueComments(issue)
+ if request.sequence_num >= len(comments):
+ raise exceptions.InputException('Invalid sequence number.')
+ we.DeleteComment(issue, comments[request.sequence_num], request.delete)
+
+ result = issues_pb2.DeleteIssueCommentResponse()
+ return result
+
+ @monorail_servicer.PRPCMethod
+ def DeleteAttachment(self, mc, request):
+ """Mark or unmark the given attachment as deleted."""
+ _project, issue, _config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, use_cache=False)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ comments = we.ListIssueComments(issue)
+ if request.sequence_num >= len(comments):
+ raise exceptions.InputException('Invalid sequence number.')
+ we.DeleteAttachment(
+ issue, comments[request.sequence_num], request.attachment_id,
+ request.delete)
+
+ result = issues_pb2.DeleteAttachmentResponse()
+ return result
+
+ @monorail_servicer.PRPCMethod
+ def FlagIssues(self, mc, request):
+ """Flag or unflag the given issues as spam."""
+ if not request.issue_refs:
+ raise exceptions.InputException('Param `issue_refs` empty.')
+
+ _project, issue_ids, _config = self._GetProjectIssueIDsAndConfig(
+ mc, request.issue_refs)
+ with work_env.WorkEnv(mc, self.services) as we:
+ issues_by_id = we.GetIssuesDict(issue_ids, use_cache=False)
+ we.FlagIssues(list(issues_by_id.values()), request.flag)
+
+ result = issues_pb2.FlagIssuesResponse()
+ return result
+
+ @monorail_servicer.PRPCMethod
+ def FlagComment(self, mc, request):
+ """Flag or unflag the given comment as spam."""
+ _project, issue, _config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, use_cache=False)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ comments = we.ListIssueComments(issue)
+ if request.sequence_num >= len(comments):
+ raise exceptions.InputException('Invalid sequence number.')
+ we.FlagComment(issue, comments[request.sequence_num], request.flag)
+
+ result = issues_pb2.FlagCommentResponse()
+ return result
+
+ @monorail_servicer.PRPCMethod
+ def ListIssuePermissions(self, mc, request):
+ """List the permissions for the current user in the given issue."""
+ project, issue, config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, use_cache=False, view_deleted=True)
+
+ perms = permissions.UpdateIssuePermissions(
+ mc.perms, project, issue, mc.auth.effective_ids, config=config)
+
+ return issues_pb2.ListIssuePermissionsResponse(
+ permissions=sorted(perms.perm_names))
+
+ @monorail_servicer.PRPCMethod
+ def MoveIssue(self, mc, request):
+ """Move an issue to another project."""
+ _project, issue, _config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, use_cache=False)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ target_project = we.GetProjectByName(request.target_project_name)
+ moved_issue = we.MoveIssue(issue, target_project)
+
+ result = issues_pb2.MoveIssueResponse(
+ new_issue_ref=converters.ConvertIssueRef(
+ (moved_issue.project_name, moved_issue.local_id)))
+ return result
+
+ @monorail_servicer.PRPCMethod
+ def CopyIssue(self, mc, request):
+ """Copy an issue."""
+ _project, issue, _config = self._GetProjectIssueAndConfig(
+ mc, request.issue_ref, use_cache=False)
+
+ with work_env.WorkEnv(mc, self.services) as we:
+ target_project = we.GetProjectByName(request.target_project_name)
+ copied_issue = we.CopyIssue(issue, target_project)
+
+ result = issues_pb2.CopyIssueResponse(
+ new_issue_ref=converters.ConvertIssueRef(
+ (copied_issue.project_name, copied_issue.local_id)))
+ return result