# 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 api_constants
from api.v3 import monorail_servicer
from api.v3 import paginator
from api.v3.api_proto import projects_pb2
from api.v3.api_proto import projects_prpc_pb2
from businesslogic import work_env


class ProjectsServicer(monorail_servicer.MonorailServicer):
  """Handle API requests related to Project 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.
  """

  DESCRIPTION = projects_prpc_pb2.ProjectsServiceDescription

  @monorail_servicer.PRPCMethod
  def ListIssueTemplates(self, mc, request):
    # type: (MonorailContext, ListIssueTemplatesRequest) ->
    #   ListIssueTemplatesResponse
    """pRPC API method that implements ListIssueTemplates.

      Raises:
        InputException if the request.parent is invalid.
        NoSuchProjectException if no project exists with the given name.
    """
    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)

    with work_env.WorkEnv(mc, self.services) as we:
      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
      project = we.GetProject(project_id)
      mc.LookupLoggedInUserPerms(project)
      templates = we.ListProjectTemplates(project_id)

    return projects_pb2.ListIssueTemplatesResponse(
        templates=self.converter.ConvertIssueTemplates(project_id, templates))

  @monorail_servicer.PRPCMethod
  def GetComponentDef(self, mc, request):
    # type: (MonorailContext, GetComponentDefRequest) ->
    #   ComponentDef
    """pRPC API method that implements GetComponentDef.

      Raises:
        InputException if the request.parent is invalid.
        NoSuchProjectException if the parent project is not found.
    """
    project_id, component_id = rnc.IngestComponentDefNames(
        mc.cnxn, [request.name], self.services)[0]

    with work_env.WorkEnv(mc, self.services) as we:
      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
      project = we.GetProject(project_id)
      mc.LookupLoggedInUserPerms(project)

      component_def = we.GetComponentDef(project_id, component_id)
      api_component_def = self.converter.ConvertComponentDef(component_def)

    return api_component_def

  @monorail_servicer.PRPCMethod
  def ListComponentDefs(self, mc, request):
    # type: (MonorailContext, ListComponentDefsRequest) ->
    #   ListComponentDefsResponse
    """pRPC API method that implements ListComponentDefs.

      Raises:
        InputException if the request.parent is invalid.
        NoSuchProjectException if the parent project is not found.
    """
    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)

    with work_env.WorkEnv(mc, self.services) as we:
      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
      project = we.GetProject(project_id)
      mc.LookupLoggedInUserPerms(project)

      page_size = paginator.CoercePageSize(
        request.page_size, api_constants.MAX_COMPONENTS_PER_PAGE)
      pager = paginator.Paginator(
          parent=request.parent, page_size=page_size)
      list_result = we.ListComponentDefs(
          project_id, page_size, pager.GetStart(request.page_token))

      api_component_defs = self.converter.ConvertComponentDefs(
          list_result.items, project_id)

    return projects_pb2.ListComponentDefsResponse(
        component_defs=api_component_defs,
        next_page_token=pager.GenerateNextPageToken(list_result.next_start))

  @monorail_servicer.PRPCMethod
  def CreateComponentDef(self, mc, request):
    # type: (MonorailContext, CreateComponentDefRequest) ->
    #   ComponentDef
    """pRPC API method that implements CreateComponentDef.

      Raises:
        InputException if the request is invalid.
        NoSuchUserException if any given component admins or ccs do not exist.
        NoSuchProjectException if the parent project does not exist.
        PermissionException if the requester is not allowed to create
          this component.
    """
    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
    admin_ids = rnc.IngestUserNames(
        mc.cnxn, request.component_def.admins, self.services)
    cc_ids = rnc.IngestUserNames(
        mc.cnxn, request.component_def.ccs, self.services)

    with work_env.WorkEnv(mc, self.services) as we:
      component_def = we.CreateComponentDef(
          project_id, request.component_def.value,
          request.component_def.docstring, admin_ids, cc_ids,
          request.component_def.labels)

    return self.converter.ConvertComponentDef(component_def)

  @monorail_servicer.PRPCMethod
  def DeleteComponentDef(self, mc, request):
    # type: (MonorailContext, DeleteComponentDefRequest) -> Empty
    """pRPC API method that implements DeleteComponentDef.

      Raises:
        InputException if the request in invalid.
        NoSuchComponentException if the component does not exist.
        PermissionException if the requester is not allowed to delete
          this component.
        NoSuchProjectException if the parent project does not exist.
    """
    project_id, component_id = rnc.IngestComponentDefNames(
        mc.cnxn, [request.name], self.services)[0]

    with work_env.WorkEnv(mc, self.services) as we:
      we.DeleteComponentDef(project_id, component_id)

    return empty_pb2.Empty()

  @monorail_servicer.PRPCMethod
  def ListProjects(self, mc, _):
    # type: (MonorailContext, ListProjectsRequest) -> ListProjectsResponse
    """pRPC API method that implements ListProjects.

      Raises:
        InputException if the request.page_token is invalid or the request does
          not match the previous request that provided the given page_token.
    """
    with work_env.WorkEnv(mc, self.services) as we:
      # 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.
      #  This method does not because it may be scoped to multiple projects.
      allowed_project_ids = we.ListProjects()
      projects_dict = we.GetProjects(allowed_project_ids)
      projects = [projects_dict[proj_id] for proj_id in allowed_project_ids]

    # TODO(crbug.com/monorail/7505): Add pagination logic.
    return projects_pb2.ListProjectsResponse(
        projects=self.converter.ConvertProjects(projects))

# 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 api_constants
from api.v3 import monorail_servicer
from api.v3 import paginator
from api.v3.api_proto import projects_pb2
from api.v3.api_proto import projects_prpc_pb2
from businesslogic import work_env


class ProjectsServicer(monorail_servicer.MonorailServicer):
  """Handle API requests related to Project 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.
  """

  DESCRIPTION = projects_prpc_pb2.ProjectsServiceDescription

  @monorail_servicer.PRPCMethod
  def ListIssueTemplates(self, mc, request):
    # type: (MonorailContext, ListIssueTemplatesRequest) ->
    #   ListIssueTemplatesResponse
    """pRPC API method that implements ListIssueTemplates.

      Raises:
        InputException if the request.parent is invalid.
        NoSuchProjectException if no project exists with the given name.
    """
    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)

    with work_env.WorkEnv(mc, self.services) as we:
      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
      project = we.GetProject(project_id)
      mc.LookupLoggedInUserPerms(project)
      templates = we.ListProjectTemplates(project_id)

    return projects_pb2.ListIssueTemplatesResponse(
        templates=self.converter.ConvertIssueTemplates(project_id, templates))

  @monorail_servicer.PRPCMethod
  def GetComponentDef(self, mc, request):
    # type: (MonorailContext, GetComponentDefRequest) ->
    #   ComponentDef
    """pRPC API method that implements GetComponentDef.

      Raises:
        InputException if the request.parent is invalid.
        NoSuchProjectException if the parent project is not found.
    """
    project_id, component_id = rnc.IngestComponentDefNames(
        mc.cnxn, [request.name], self.services)[0]

    with work_env.WorkEnv(mc, self.services) as we:
      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
      project = we.GetProject(project_id)
      mc.LookupLoggedInUserPerms(project)

      component_def = we.GetComponentDef(project_id, component_id)
      api_component_def = self.converter.ConvertComponentDef(component_def)

    return api_component_def

  @monorail_servicer.PRPCMethod
  def ListComponentDefs(self, mc, request):
    # type: (MonorailContext, ListComponentDefsRequest) ->
    #   ListComponentDefsResponse
    """pRPC API method that implements ListComponentDefs.

      Raises:
        InputException if the request.parent is invalid.
        NoSuchProjectException if the parent project is not found.
    """
    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)

    with work_env.WorkEnv(mc, self.services) as we:
      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
      project = we.GetProject(project_id)
      mc.LookupLoggedInUserPerms(project)

      page_size = paginator.CoercePageSize(
        request.page_size, api_constants.MAX_COMPONENTS_PER_PAGE)
      pager = paginator.Paginator(
          parent=request.parent, page_size=page_size)
      list_result = we.ListComponentDefs(
          project_id, page_size, pager.GetStart(request.page_token))

      api_component_defs = self.converter.ConvertComponentDefs(
          list_result.items, project_id)

    return projects_pb2.ListComponentDefsResponse(
        component_defs=api_component_defs,
        next_page_token=pager.GenerateNextPageToken(list_result.next_start))

  @monorail_servicer.PRPCMethod
  def CreateComponentDef(self, mc, request):
    # type: (MonorailContext, CreateComponentDefRequest) ->
    #   ComponentDef
    """pRPC API method that implements CreateComponentDef.

      Raises:
        InputException if the request is invalid.
        NoSuchUserException if any given component admins or ccs do not exist.
        NoSuchProjectException if the parent project does not exist.
        PermissionException if the requester is not allowed to create
          this component.
    """
    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
    admin_ids = rnc.IngestUserNames(
        mc.cnxn, request.component_def.admins, self.services)
    cc_ids = rnc.IngestUserNames(
        mc.cnxn, request.component_def.ccs, self.services)

    with work_env.WorkEnv(mc, self.services) as we:
      component_def = we.CreateComponentDef(
          project_id, request.component_def.value,
          request.component_def.docstring, admin_ids, cc_ids,
          request.component_def.labels)

    return self.converter.ConvertComponentDef(component_def)

  @monorail_servicer.PRPCMethod
  def DeleteComponentDef(self, mc, request):
    # type: (MonorailContext, DeleteComponentDefRequest) -> Empty
    """pRPC API method that implements DeleteComponentDef.

      Raises:
        InputException if the request in invalid.
        NoSuchComponentException if the component does not exist.
        PermissionException if the requester is not allowed to delete
          this component.
        NoSuchProjectException if the parent project does not exist.
    """
    project_id, component_id = rnc.IngestComponentDefNames(
        mc.cnxn, [request.name], self.services)[0]

    with work_env.WorkEnv(mc, self.services) as we:
      we.DeleteComponentDef(project_id, component_id)

    return empty_pb2.Empty()

  @monorail_servicer.PRPCMethod
  def ListProjects(self, mc, _):
    # type: (MonorailContext, ListProjectsRequest) -> ListProjectsResponse
    """pRPC API method that implements ListProjects.

      Raises:
        InputException if the request.page_token is invalid or the request does
          not match the previous request that provided the given page_token.
    """
    with work_env.WorkEnv(mc, self.services) as we:
      # 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.
      #  This method does not because it may be scoped to multiple projects.
      allowed_project_ids = we.ListProjects()
      projects_dict = we.GetProjects(allowed_project_ids)
      projects = [projects_dict[proj_id] for proj_id in allowed_project_ids]

    # TODO(crbug.com/monorail/7505): Add pagination logic.
    return projects_pb2.ListProjectsResponse(
        projects=self.converter.ConvertProjects(projects))

# 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 Handle API requests related to Project 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. # type: (MonorailContext, ListIssueTemplatesRequest) -> #   ListIssueTemplatesResponse pRPC API method that implements ListIssueTemplates.        Raises:         InputException if the request.parent is invalid.         NoSuchProjectException if no project exists with the given name. # TODO(crbug/monorail/7614): Eliminate the need to do this lookup. # type: (MonorailContext, GetComponentDefRequest) -> #   ComponentDef pRPC API method that implements GetComponentDef.        Raises:         InputException if the request.parent is invalid.         NoSuchProjectException if the parent project is not found. # TODO(crbug/monorail/7614): Eliminate the need to do this lookup. # type: (MonorailContext, ListComponentDefsRequest) -> #   ListComponentDefsResponse pRPC API method that implements ListComponentDefs.        Raises:         InputException if the request.parent is invalid.         NoSuchProjectException if the parent project is not found. # TODO(crbug/monorail/7614): Eliminate the need to do this lookup. # type: (MonorailContext, CreateComponentDefRequest) -> #   ComponentDef pRPC API method that implements CreateComponentDef.        Raises:         InputException if the request is invalid.         NoSuchUserException if any given component admins or ccs do not exist.         NoSuchProjectException if the parent project does not exist.         PermissionException if the requester is not allowed to create           this component. # type: (MonorailContext, DeleteComponentDefRequest) -> Empty pRPC API method that implements DeleteComponentDef.        Raises:         InputException if the request in invalid.         NoSuchComponentException if the component does not exist.         PermissionException if the requester is not allowed to delete           this component.         NoSuchProjectException if the parent project does not exist. # type: (MonorailContext, ListProjectsRequest) -> ListProjectsResponse pRPC API method that implements ListProjects.        Raises:         InputException if the request.page_token is invalid or the request does           not match the previous request that provided the given page_token. # 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. #  This method does not because it may be scoped to multiple projects. # TODO(crbug.com/monorail/7505): Add pagination logic.