Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1 | # Copyright 2018 The Chromium Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style |
| 3 | # license that can be found in the LICENSE file or at |
| 4 | # https://developers.google.com/open-source/licenses/bsd |
| 5 | |
| 6 | from __future__ import print_function |
| 7 | from __future__ import division |
| 8 | from __future__ import absolute_import |
| 9 | |
| 10 | import logging |
| 11 | |
| 12 | import settings |
| 13 | |
| 14 | from api import monorail_servicer |
| 15 | from api import converters |
| 16 | from api.api_proto import projects_pb2 |
| 17 | from api.api_proto import project_objects_pb2 |
| 18 | from api.api_proto import projects_prpc_pb2 |
| 19 | from businesslogic import work_env |
| 20 | from framework import framework_bizobj |
| 21 | from framework import exceptions |
| 22 | from framework import framework_views |
| 23 | from framework import permissions |
| 24 | from project import project_helpers |
| 25 | from tracker import tracker_bizobj |
| 26 | from tracker import tracker_helpers |
| 27 | |
| 28 | # TODO(zhangtiff): Remove dependency on tracker_views. |
| 29 | from tracker import tracker_views |
| 30 | |
| 31 | |
| 32 | class ProjectsServicer(monorail_servicer.MonorailServicer): |
| 33 | """Handle API requests related to Project objects. |
| 34 | |
| 35 | Each API request is implemented with a method as defined in the .proto |
| 36 | file that does any request-specific validation, uses work_env to |
| 37 | safely operate on business objects, and returns a response proto. |
| 38 | """ |
| 39 | |
| 40 | DESCRIPTION = projects_prpc_pb2.ProjectsServiceDescription |
| 41 | |
| 42 | def _GetProject(self, mc, request, use_cache=True): |
| 43 | """Get the project object specified in the request.""" |
| 44 | with work_env.WorkEnv(mc, self.services, phase='getting project') as we: |
| 45 | project = we.GetProjectByName(request.project_name, use_cache=use_cache) |
| 46 | # Perms in this project are already looked up in MonorailServicer. |
| 47 | return project |
| 48 | |
| 49 | @monorail_servicer.PRPCMethod |
| 50 | def ListProjects(self, _mc, _request): |
| 51 | return projects_pb2.ListProjectsResponse( |
| 52 | projects=[ |
| 53 | project_objects_pb2.Project(name='One'), |
| 54 | project_objects_pb2.Project(name='Two')], |
| 55 | next_page_token='next...') |
| 56 | |
| 57 | @monorail_servicer.PRPCMethod |
| 58 | def ListProjectTemplates(self, mc, request): |
| 59 | """Return the specific project's templates.""" |
| 60 | if not request.project_name: |
| 61 | raise exceptions.InputException('Param `project_name` required.') |
| 62 | project = self._GetProject(mc, request) |
| 63 | |
| 64 | with work_env.WorkEnv(mc, self.services) as we: |
| 65 | config = we.GetProjectConfig(project.project_id) |
| 66 | templates = we.ListProjectTemplates(project.project_id) |
| 67 | |
| 68 | with mc.profiler.Phase('converting to response objects'): |
| 69 | involved_user_ids = tracker_bizobj.UsersInvolvedInTemplates(templates) |
| 70 | users_by_id = framework_views.MakeAllUserViews( |
| 71 | mc.cnxn, self.services.user, involved_user_ids) |
| 72 | response = projects_pb2.ListProjectTemplatesResponse( |
| 73 | templates=converters.ConvertProjectTemplateDefs( |
| 74 | templates, users_by_id, config)) |
| 75 | |
| 76 | return response |
| 77 | |
| 78 | @monorail_servicer.PRPCMethod |
| 79 | def GetConfig(self, mc, request): |
| 80 | """Return the specified project config.""" |
| 81 | project = self._GetProject(mc, request) |
| 82 | |
| 83 | with work_env.WorkEnv(mc, self.services) as we: |
| 84 | config = we.GetProjectConfig(project.project_id) |
| 85 | |
| 86 | with mc.profiler.Phase('making user views'): |
| 87 | involved_user_ids = tracker_bizobj.UsersInvolvedInConfig(config) |
| 88 | users_by_id = framework_views.MakeAllUserViews( |
| 89 | mc.cnxn, self.services.user, involved_user_ids) |
| 90 | framework_views.RevealAllEmailsToMembers( |
| 91 | mc.cnxn, self.services, mc.auth, users_by_id, project) |
| 92 | |
| 93 | label_ids = tracker_bizobj.LabelIDsInvolvedInConfig(config) |
| 94 | labels_by_id = { |
| 95 | label_id: self.services.config.LookupLabel( |
| 96 | mc.cnxn, config.project_id, label_id) |
| 97 | for label_id in label_ids} |
| 98 | |
| 99 | result = converters.ConvertConfig( |
| 100 | project, config, users_by_id, labels_by_id) |
| 101 | return result |
| 102 | |
| 103 | @monorail_servicer.PRPCMethod |
| 104 | def GetPresentationConfig(self, mc, request): |
| 105 | """Return the UI centric pieces of the project config.""" |
| 106 | project = self._GetProject(mc, request) |
| 107 | |
| 108 | with work_env.WorkEnv(mc, self.services) as we: |
| 109 | config = we.GetProjectConfig(project.project_id) |
| 110 | |
| 111 | project_thumbnail_url = tracker_views.LogoView(project).thumbnail_url |
| 112 | project_summary = project.summary |
| 113 | custom_issue_entry_url = config.custom_issue_entry_url |
| 114 | revision_url_format = ( |
| 115 | project.revision_url_format or settings.revision_url_format) |
| 116 | |
| 117 | default_query = None |
| 118 | saved_queries = None |
| 119 | |
| 120 | # Only show default query or project saved queries for project |
| 121 | # members, in case they contain sensitive information. |
| 122 | if framework_bizobj.UserIsInProject( |
| 123 | project, mc.auth.effective_ids): |
| 124 | default_query = config.member_default_query |
| 125 | |
| 126 | saved_queries = self.services.features.GetCannedQueriesByProjectID( |
| 127 | mc.cnxn, project.project_id) |
| 128 | |
| 129 | return project_objects_pb2.PresentationConfig( |
| 130 | project_thumbnail_url=project_thumbnail_url, |
| 131 | project_summary=project_summary, |
| 132 | custom_issue_entry_url=custom_issue_entry_url, |
| 133 | default_query=default_query, |
| 134 | default_col_spec=config.default_col_spec, |
| 135 | default_sort_spec=config.default_sort_spec, |
| 136 | default_x_attr=config.default_x_attr, |
| 137 | default_y_attr=config.default_y_attr, |
| 138 | saved_queries=converters.IngestSavedQueries( |
| 139 | mc.cnxn, self.services.project, saved_queries), |
| 140 | revision_url_format=revision_url_format) |
| 141 | |
| 142 | @monorail_servicer.PRPCMethod |
| 143 | def GetCustomPermissions(self, mc, request): |
| 144 | """Return the custom permissions for the given project.""" |
| 145 | project = self._GetProject(mc, request) |
| 146 | custom_permissions = permissions.GetCustomPermissions(project) |
| 147 | |
| 148 | result = projects_pb2.GetCustomPermissionsResponse( |
| 149 | permissions=custom_permissions) |
| 150 | return result |
| 151 | |
| 152 | @monorail_servicer.PRPCMethod |
| 153 | def GetVisibleMembers(self, mc, request): |
| 154 | """Return the members of the project that the user can see. |
| 155 | |
| 156 | Raises: |
| 157 | PermissionException the user is not allowed to view the project members. |
| 158 | """ |
| 159 | project = self._GetProject(mc, request) |
| 160 | if not permissions.CanViewContributorList(mc, project): |
| 161 | raise permissions.PermissionException( |
| 162 | 'User is not allowed to view the project members') |
| 163 | |
| 164 | users_by_id = tracker_helpers.GetVisibleMembers(mc, project, self.services) |
| 165 | |
| 166 | sorted_user_ids = sorted( |
| 167 | users_by_id, key=lambda uid: users_by_id[uid].email) |
| 168 | user_refs = converters.ConvertUserRefs( |
| 169 | sorted_user_ids, [], users_by_id, True) |
| 170 | sorted_group_ids = sorted( |
| 171 | (uv.user_id for uv in users_by_id.values() if uv.is_group), |
| 172 | key=lambda uid: users_by_id[uid].email) |
| 173 | group_refs = converters.ConvertUserRefs( |
| 174 | sorted_group_ids, [], users_by_id, True) |
| 175 | |
| 176 | result = projects_pb2.GetVisibleMembersResponse( |
| 177 | user_refs=user_refs, group_refs=group_refs) |
| 178 | return result |
| 179 | |
| 180 | @monorail_servicer.PRPCMethod |
| 181 | def GetLabelOptions(self, mc, request): |
| 182 | """Return the label options for autocomplete for the given project.""" |
| 183 | project = self._GetProject(mc, request) |
| 184 | |
| 185 | with work_env.WorkEnv(mc, self.services) as we: |
| 186 | config = we.GetProjectConfig(project.project_id) |
| 187 | |
| 188 | label_options = tracker_helpers.GetLabelOptions( |
| 189 | config, permissions.GetCustomPermissions(project)) |
| 190 | label_defs = [ |
| 191 | project_objects_pb2.LabelDef( |
| 192 | label=label['name'], |
| 193 | docstring=label['doc']) |
| 194 | for label in label_options] |
| 195 | |
| 196 | result = projects_pb2.GetLabelOptionsResponse( |
| 197 | label_options=label_defs, |
| 198 | exclusive_label_prefixes=config.exclusive_label_prefixes) |
| 199 | return result |
| 200 | |
| 201 | @monorail_servicer.PRPCMethod |
| 202 | def ListStatuses(self, mc, request): |
| 203 | """Return all well-known statuses in the specified project.""" |
| 204 | project = self._GetProject(mc, request) |
| 205 | |
| 206 | with work_env.WorkEnv(mc, self.services) as we: |
| 207 | config = we.GetProjectConfig(project.project_id) |
| 208 | |
| 209 | status_defs = [ |
| 210 | converters.ConvertStatusDef(sd) |
| 211 | for sd in config.well_known_statuses] |
| 212 | statuses_offer_merge = [ |
| 213 | converters.ConvertStatusRef(sd.status, None, config) |
| 214 | for sd in config.well_known_statuses |
| 215 | if sd.status in config.statuses_offer_merge] |
| 216 | |
| 217 | result = projects_pb2.ListStatusesResponse( |
| 218 | status_defs=status_defs, |
| 219 | statuses_offer_merge=statuses_offer_merge, |
| 220 | restrict_to_known=config.restrict_to_known) |
| 221 | return result |
| 222 | |
| 223 | @monorail_servicer.PRPCMethod |
| 224 | def ListComponents(self, mc, request): |
| 225 | """Return all component defs in the specified project.""" |
| 226 | project = self._GetProject(mc, request) |
| 227 | |
| 228 | with work_env.WorkEnv(mc, self.services) as we: |
| 229 | config = we.GetProjectConfig(project.project_id) |
| 230 | |
| 231 | with mc.profiler.Phase('making user views'): |
| 232 | users_by_id = {} |
| 233 | if request.include_admin_info: |
| 234 | users_involved = tracker_bizobj.UsersInvolvedInConfig(config) |
| 235 | users_by_id = framework_views.MakeAllUserViews( |
| 236 | mc.cnxn, self.services.user, users_involved) |
| 237 | framework_views.RevealAllEmailsToMembers( |
| 238 | mc.cnxn, self.services, mc.auth, users_by_id, project) |
| 239 | |
| 240 | with mc.profiler.Phase('looking up labels'): |
| 241 | labels_by_id = {} |
| 242 | if request.include_admin_info: |
| 243 | label_ids = tracker_bizobj.LabelIDsInvolvedInConfig(config) |
| 244 | labels_by_id = { |
| 245 | label_id: self.services.config.LookupLabel( |
| 246 | mc.cnxn, config.project_id, label_id) |
| 247 | for label_id in label_ids} |
| 248 | |
| 249 | component_defs = [ |
| 250 | converters.ConvertComponentDef( |
| 251 | cd, users_by_id, labels_by_id, request.include_admin_info) |
| 252 | for cd in config.component_defs] |
| 253 | |
| 254 | result = projects_pb2.ListComponentsResponse( |
| 255 | component_defs=component_defs) |
| 256 | return result |
| 257 | |
| 258 | @monorail_servicer.PRPCMethod |
| 259 | def ListFields(self, mc, request): |
| 260 | """List all fields for the specified project.""" |
| 261 | project = self._GetProject(mc, request) |
| 262 | |
| 263 | with work_env.WorkEnv(mc, self.services) as we: |
| 264 | config = we.GetProjectConfig(project.project_id) |
| 265 | |
| 266 | users_by_id = {} |
| 267 | users_for_perm = {} |
| 268 | # Only look for members if user choices are requested and there are user |
| 269 | # fields that need permissions. |
| 270 | if request.include_user_choices: |
| 271 | perms_needed = { |
| 272 | fd.needs_perm |
| 273 | for fd in config.field_defs |
| 274 | if fd.needs_perm and not fd.is_deleted} |
| 275 | if perms_needed: |
| 276 | users_by_id = tracker_helpers.GetVisibleMembers( |
| 277 | mc, project, self.services) |
| 278 | effective_ids_by_user = self.services.usergroup.LookupAllMemberships( |
| 279 | mc.cnxn, users_by_id) |
| 280 | users_for_perm = project_helpers.UsersWithPermsInProject( |
| 281 | project, perms_needed, users_by_id, effective_ids_by_user) |
| 282 | |
| 283 | field_defs = [ |
| 284 | converters.ConvertFieldDef( |
| 285 | fd, users_for_perm.get(fd.needs_perm, []), users_by_id, config, |
| 286 | request.include_admin_info) |
| 287 | for fd in config.field_defs |
| 288 | if not fd.is_deleted] |
| 289 | |
| 290 | result = projects_pb2.ListFieldsResponse(field_defs=field_defs) |
| 291 | return result |
| 292 | |
| 293 | @monorail_servicer.PRPCMethod |
| 294 | def GetProjectStarCount(self, mc, request): |
| 295 | """Get the star count for the specified project.""" |
| 296 | project = self._GetProject(mc, request) |
| 297 | |
| 298 | with work_env.WorkEnv(mc, self.services) as we: |
| 299 | star_count = we.GetProjectStarCount(project.project_id) |
| 300 | |
| 301 | result = projects_pb2.GetProjectStarCountResponse(star_count=star_count) |
| 302 | return result |
| 303 | |
| 304 | @monorail_servicer.PRPCMethod |
| 305 | def StarProject(self, mc, request): |
| 306 | """Star the specified project.""" |
| 307 | project = self._GetProject(mc, request) |
| 308 | |
| 309 | with work_env.WorkEnv(mc, self.services) as we: |
| 310 | we.StarProject(project.project_id, request.starred) |
| 311 | star_count = we.GetProjectStarCount(project.project_id) |
| 312 | |
| 313 | result = projects_pb2.StarProjectResponse(star_count=star_count) |
| 314 | return result |
| 315 | |
| 316 | @monorail_servicer.PRPCMethod |
| 317 | def CheckProjectName(self, mc, request): |
| 318 | """Check that a project name is valid and not already in use.""" |
| 319 | with work_env.WorkEnv(mc, self.services) as we: |
| 320 | error = we.CheckProjectName(request.project_name) |
| 321 | result = projects_pb2.CheckProjectNameResponse(error=error) |
| 322 | return result |
| 323 | |
| 324 | @monorail_servicer.PRPCMethod |
| 325 | def CheckComponentName(self, mc, request): |
| 326 | """Check that the component name is valid and not already in use.""" |
| 327 | project = self._GetProject(mc, request) |
| 328 | with work_env.WorkEnv(mc, self.services) as we: |
| 329 | error = we.CheckComponentName( |
| 330 | project.project_id, request.parent_path, request.component_name) |
| 331 | result = projects_pb2.CheckComponentNameResponse(error=error) |
| 332 | return result |
| 333 | |
| 334 | @monorail_servicer.PRPCMethod |
| 335 | def CheckFieldName(self, mc, request): |
| 336 | """Check that a field name is valid and not already in use.""" |
| 337 | project = self._GetProject(mc, request) |
| 338 | with work_env.WorkEnv(mc, self.services) as we: |
| 339 | error = we.CheckFieldName(project.project_id, request.field_name) |
| 340 | result = projects_pb2.CheckFieldNameResponse(error=error) |
| 341 | return result |