Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/v3/hotlists_servicer.py b/api/v3/hotlists_servicer.py
new file mode 100644
index 0000000..2ea2a31
--- /dev/null
+++ b/api/v3/hotlists_servicer.py
@@ -0,0 +1,266 @@
+# Copyright 2020 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
+
+from google.protobuf import empty_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import monorail_servicer
+from api.v3.api_proto import feature_objects_pb2
+from api.v3.api_proto import hotlists_pb2
+from api.v3.api_proto import hotlists_prpc_pb2
+from businesslogic import work_env
+from framework import exceptions
+from features import features_constants
+from tracker import tracker_constants
+
+
+class HotlistsServicer(monorail_servicer.MonorailServicer):
+  """Handle API requests related to Hotlist objects.
+  Each API request is implemented with a method as defined in the
+  .proto file. Each method does any request-specific validation, uses work_env
+  to safely operate on business objects, and returns a response proto.
+  """
+  # NOTE(crbug/monorail/7614): Until the referenced cleanup is complete,
+  # all servicer methods that are scoped to a single Project need to call
+  # mc.LookupLoggedInUserPerms.
+  # Methods in this file do not because hotlists can span projects.
+
+  DESCRIPTION = hotlists_prpc_pb2.HotlistsServiceDescription
+
+  @monorail_servicer.PRPCMethod
+  def ListHotlistItems(self, mc, request):
+    # type: (MonorailContext, ListHotlistItemsRequest) ->
+    #     ListHotlistItemsResponse
+    """pRPC API method that implements ListHotlistItems.
+
+      Raises:
+        NoSuchHotlistException if the hotlist is not found.
+        PermissionException if the user is not allowed to view the hotlist.
+        InputException if the request.page_token is invalid, the request does
+          not match the previous request that provided the given page_token, or
+          the page_size is a negative value.
+    """
+    hotlist_id = rnc.IngestHotlistName(request.parent)
+    if request.page_size < 0:
+      raise exceptions.InputException('`page_size` cannot be negative.')
+    page_size = request.page_size
+    if (not request.page_size or
+        request.page_size > features_constants.DEFAULT_RESULTS_PER_PAGE):
+      page_size = features_constants.DEFAULT_RESULTS_PER_PAGE
+
+    # TODO(crbug/monorail/7104): take start from request.page_token
+    start = 0
+    sort_spec = request.order_by.replace(',', ' ')
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      list_result = we.ListHotlistItems(
+          hotlist_id, page_size, start,
+          tracker_constants.ALL_ISSUES_CAN, sort_spec, '')
+
+    # TODO(crbug/monorail/7104): plug in next_page_token when it's been
+    # implemented.
+    next_page_token = ''
+    return hotlists_pb2.ListHotlistItemsResponse(
+        items=self.converter.ConvertHotlistItems(hotlist_id, list_result.items),
+        next_page_token=next_page_token)
+
+
+  @monorail_servicer.PRPCMethod
+  def RerankHotlistItems(self, mc, request):
+    # type: (MonorailContext, RerankHotlistItemsRequest) -> Empty
+    """pRPC API method that implements RerankHotlistItems.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to rerank the hotlist.
+      InputException if request.target_position is invalid or
+        request.hotlist_items is empty or contains invalid items.
+      NoSuchIssueException if hotlist item does not exist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.name)
+    moved_issue_ids = rnc.IngestHotlistItemNames(
+        mc.cnxn, request.hotlist_items, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.RerankHotlistItems(
+          hotlist_id, moved_issue_ids, request.target_position)
+
+    return empty_pb2.Empty()
+
+
+  @monorail_servicer.PRPCMethod
+  def RemoveHotlistItems(self, mc, request):
+    # type: (MonorailContext, RemoveHotlistItemsRequest) -> Empty
+    """pPRC API method that implements RemoveHotlistItems.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to edit the hotlist.
+      InputException if the items to be removed are not found in the hotlist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.parent)
+    remove_issue_ids = rnc.IngestIssueNames(
+        mc.cnxn, request.issues, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.RemoveHotlistItems(hotlist_id, remove_issue_ids)
+
+    return empty_pb2.Empty()
+
+
+  @monorail_servicer.PRPCMethod
+  def AddHotlistItems(self, mc, request):
+    # type: (MonorailContext, AddHotlistItemsRequest) -> Empty
+    """pRPC API method that implements AddHotlistItems.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to edit the hotlist.
+      InputException if the request.target_position is invalid or the given
+        list of issues to add is empty or invalid.
+    """
+    hotlist_id = rnc.IngestHotlistName(request.parent)
+    new_issue_ids = rnc.IngestIssueNames(mc.cnxn, request.issues, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.AddHotlistItems(hotlist_id, new_issue_ids, request.target_position)
+
+    return empty_pb2.Empty()
+
+
+  @monorail_servicer.PRPCMethod
+  def RemoveHotlistEditors(self, mc, request):
+    # type: (MonorailContext, RemoveHotlistEditorsRequest) -> Empty
+    """pPRC API method that implements RemoveHotlistEditors.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to edit the hotlist.
+      InputException if the editors to be removed are not found in the hotlist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.name)
+    remove_user_ids = rnc.IngestUserNames(
+        mc.cnxn, request.editors, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.RemoveHotlistEditors(hotlist_id, remove_user_ids)
+
+    return empty_pb2.Empty()
+
+
+  @monorail_servicer.PRPCMethod
+  def GetHotlist(self, mc, request):
+    # type: (MonorailContext, GetHotlistRequest) -> Hotlist
+    """pRPC API method that implements GetHotlist.
+
+    Raises:
+      InputException if the given name does not have a valid format.
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to view the hotlist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.name)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      hotlist = we.GetHotlist(hotlist_id)
+
+    return self.converter.ConvertHotlist(hotlist)
+
+  @monorail_servicer.PRPCMethod
+  def GatherHotlistsForUser(self, mc, request):
+    # type: (MonorailContext, GatherHotlistsForUserRequest)
+    #   -> GatherHotlistsForUserResponse
+    """pRPC API method that implements GatherHotlistsForUser.
+
+    Raises:
+      NoSuchUserException if the user is not found.
+      InputException if some request parameters are invalid.
+    """
+
+    user_id = rnc.IngestUserName(mc.cnxn, request.user, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      hotlists = we.ListHotlistsByUser(user_id)
+
+    return hotlists_pb2.GatherHotlistsForUserResponse(
+        hotlists=self.converter.ConvertHotlists(hotlists))
+
+  @monorail_servicer.PRPCMethod
+  def UpdateHotlist(self, mc, request):
+    # type: (MonorailContext, UpdateHotlistRequest) -> UpdateHotlistResponse
+    """pRPC API method that implements UpdateHotlist.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to make this update.
+      InputException if some request parameters are required and missing or
+        invalid.
+    """
+    if not request.update_mask:
+      raise exceptions.InputException('No paths given in `update_mask`.')
+    if not request.hotlist:
+      raise exceptions.InputException('No `hotlist` param given.')
+
+    if not request.update_mask.IsValidForDescriptor(
+        feature_objects_pb2.Hotlist.DESCRIPTOR):
+      raise exceptions.InputException('Invalid `update_mask` for `hotlist`')
+
+    hotlist_id = rnc.IngestHotlistName(request.hotlist.name)
+
+    update_args = {}
+    hotlist = request.hotlist
+    for path in request.update_mask.paths:
+      if path == 'display_name':
+        update_args['hotlist_name'] = hotlist.display_name
+      elif path == 'owner':
+        owner_id = rnc.IngestUserName(mc.cnxn, hotlist.owner, self.services)
+        update_args['owner_id'] = owner_id
+      elif path == 'editors':
+        add_editor_ids = rnc.IngestUserNames(
+            mc.cnxn, hotlist.editors, self.services)
+        update_args['add_editor_ids'] = add_editor_ids
+      elif path == 'summary':
+        update_args['summary'] = hotlist.summary
+      elif path == 'description':
+        update_args['description'] = hotlist.description
+      elif path == 'hotlist_privacy':
+        update_args['is_private'] = (
+            hotlist.hotlist_privacy == feature_objects_pb2.Hotlist
+            .HotlistPrivacy.Value('PRIVATE'))
+      elif path == 'default_columns':
+        update_args[
+            'default_col_spec'] = self.converter.IngestIssuesListColumns(
+                hotlist.default_columns)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.UpdateHotlist(hotlist_id, **update_args)
+      hotlist = we.GetHotlist(hotlist_id, use_cache=False)
+
+    return self.converter.ConvertHotlist(hotlist)
+
+  @monorail_servicer.PRPCMethod
+  def DeleteHotlist(self, mc, request):
+    # type: (MonorailContext, GetHotlistRequest) -> Empty
+    """pRPC API method that implements DeleteHotlist.
+
+    Raises:
+      InputException if the given name does not have a valid format.
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to delete the hotlist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.name)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.DeleteHotlist(hotlist_id)
+
+    return empty_pb2.Empty()