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