OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is govered 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 """Convert Monorail PB objects to API PB objects""" |
| 7 |
| 8 import datetime |
| 9 import logging |
| 10 |
| 11 from framework import framework_constants |
| 12 from framework import framework_helpers |
| 13 from framework import permissions |
| 14 from proto import api_pb2_v1 |
| 15 from proto import project_pb2 |
| 16 from proto import tracker_pb2 |
| 17 from services import issue_svc |
| 18 from services import project_svc |
| 19 from services import user_svc |
| 20 from tracker import tracker_bizobj |
| 21 from tracker import tracker_helpers |
| 22 |
| 23 |
| 24 def convert_project(project, config, role): |
| 25 """Convert Monorail Project PB to API ProjectWrapper PB.""" |
| 26 |
| 27 return api_pb2_v1.ProjectWrapper( |
| 28 kind='monorail#project', |
| 29 name=project.project_name, |
| 30 externalId=project.project_name, |
| 31 htmlLink='/p/%s/' % project.project_name, |
| 32 summary=project.summary, |
| 33 description=project.description, |
| 34 role=role, |
| 35 issuesConfig=convert_project_config(config)) |
| 36 |
| 37 |
| 38 def convert_project_config(config): |
| 39 """Convert Monorail ProjectIssueConfig PB to API ProjectIssueConfig PB.""" |
| 40 |
| 41 return api_pb2_v1.ProjectIssueConfig( |
| 42 kind='monorail#projectIssueConfig', |
| 43 restrictToKnown=config.restrict_to_known, |
| 44 defaultColumns=config.default_col_spec.split(), |
| 45 defaultSorting=config.default_sort_spec.split(), |
| 46 statuses=[convert_status(s) for s in config.well_known_statuses], |
| 47 labels=[convert_label(l) for l in config.well_known_labels], |
| 48 prompts=[convert_template(t) for t in config.templates], |
| 49 defaultPromptForMembers=config.default_template_for_developers, |
| 50 defaultPromptForNonMembers=config.default_template_for_users) |
| 51 |
| 52 |
| 53 def convert_status(status): |
| 54 """Convert Monorail StatusDef PB to API Status PB.""" |
| 55 |
| 56 return api_pb2_v1.Status( |
| 57 status=status.status, |
| 58 meansOpen=status.means_open, |
| 59 description=status.status_docstring) |
| 60 |
| 61 |
| 62 def convert_label(label): |
| 63 """Convert Monorail LabelDef PB to API Label PB.""" |
| 64 |
| 65 return api_pb2_v1.Label( |
| 66 label=label.label, |
| 67 description=label.label_docstring) |
| 68 |
| 69 |
| 70 def convert_template(template): |
| 71 """Convert Monorail TemplateDef PB to API Prompt PB.""" |
| 72 |
| 73 return api_pb2_v1.Prompt( |
| 74 name=template.name, |
| 75 title=template.summary, |
| 76 description=template.content, |
| 77 titleMustBeEdited=template.summary_must_be_edited, |
| 78 status=template.status, |
| 79 labels=template.labels, |
| 80 membersOnly=template.members_only, |
| 81 defaultToMember=template.owner_defaults_to_member, |
| 82 componentRequired=template.component_required) |
| 83 |
| 84 |
| 85 def convert_person(user_id, cnxn, services, trap_exception=False): |
| 86 """Convert user id to API AtomPerson PB.""" |
| 87 |
| 88 if not user_id: |
| 89 return None |
| 90 user_email = None |
| 91 try: |
| 92 user_email = services.user.LookupUserEmail(cnxn, user_id) |
| 93 except user_svc.NoSuchUserException as ex: |
| 94 if trap_exception: |
| 95 logging.warning(str(ex)) |
| 96 return None |
| 97 else: |
| 98 raise ex |
| 99 return api_pb2_v1.AtomPerson( |
| 100 kind='monorail#issuePerson', |
| 101 name=user_email, |
| 102 htmlLink='https://%s/u/%d' % (framework_helpers.GetHostPort(), user_id)) |
| 103 |
| 104 |
| 105 def convert_issue_ids(issue_ids, mar, services): |
| 106 """Convert global issue ids to API IssueRef PB.""" |
| 107 |
| 108 # missed issue ids are filtered out. |
| 109 issues = services.issue.GetIssues(mar.cnxn, issue_ids) |
| 110 result = [] |
| 111 for issue in issues: |
| 112 issue_ref = api_pb2_v1.IssueRef( |
| 113 issueId=issue.local_id, |
| 114 projectId=issue.project_name, |
| 115 kind='monorail#issueRef') |
| 116 result.append(issue_ref) |
| 117 return result |
| 118 |
| 119 |
| 120 def convert_issueref_pbs(issueref_pbs, mar, services): |
| 121 """Convert API IssueRef PBs to global issue ids.""" |
| 122 |
| 123 if issueref_pbs: |
| 124 result = [] |
| 125 for ir in issueref_pbs: |
| 126 project_id = mar.project_id |
| 127 if ir.projectId: |
| 128 project = services.project.GetProjectByName( |
| 129 mar.cnxn, ir.projectId) |
| 130 if project: |
| 131 project_id = project.project_id |
| 132 try: |
| 133 issue = services.issue.GetIssueByLocalID( |
| 134 mar.cnxn, project_id, ir.issueId) |
| 135 result.append(issue.issue_id) |
| 136 except issue_svc.NoSuchIssueException: |
| 137 logging.warning( |
| 138 'Issue (%s:%d) does not exist.' % (ir.projectId, ir.issueId)) |
| 139 return result |
| 140 else: |
| 141 return None |
| 142 |
| 143 |
| 144 def convert_issue(cls, issue, mar, services): |
| 145 """Convert Monorail Issue PB to API IssuesGetInsertResponse.""" |
| 146 |
| 147 config = services.config.GetProjectConfig(mar.cnxn, issue.project_id) |
| 148 granted_perms = tracker_bizobj.GetGrantedPerms( |
| 149 issue, mar.auth.effective_ids, config) |
| 150 issue_project = services.project.GetProject(mar.cnxn, issue.project_id) |
| 151 component_list = [] |
| 152 for cd in config.component_defs: |
| 153 cid = cd.component_id |
| 154 if cid in issue.component_ids: |
| 155 component_list.append(cd.path) |
| 156 cc_list = [convert_person(p, mar.cnxn, services) for p in issue.cc_ids] |
| 157 cc_list = [p for p in cc_list if p is not None] |
| 158 field_values_list = [] |
| 159 field_id_dict = { |
| 160 fd.field_id: fd.field_name for fd in config.field_defs} |
| 161 for fv in issue.field_values: |
| 162 field_name = field_id_dict.get(fv.field_id) |
| 163 if not field_name: |
| 164 logging.warning('Custom field %d of project %s does not exist', |
| 165 fv.field_id, issue_project.project_name) |
| 166 continue |
| 167 val = None |
| 168 if fv.user_id: |
| 169 try: |
| 170 val = services.user.LookupUserEmail(mar.cnxn, fv.user_id) |
| 171 except user_svc.NoSuchUserException: |
| 172 val = '' |
| 173 elif fv.str_value: |
| 174 val = fv.str_value |
| 175 elif fv.int_value: |
| 176 val = str(fv.int_value) |
| 177 new_fv = api_pb2_v1.FieldValue( |
| 178 fieldName=field_name, |
| 179 fieldValue=val, |
| 180 derived=fv.derived) |
| 181 field_values_list.append(new_fv) |
| 182 resp = cls( |
| 183 kind='monorail#issue', |
| 184 id=issue.local_id, |
| 185 title=issue.summary, |
| 186 summary=issue.summary, |
| 187 projectId=issue_project.project_name, |
| 188 stars=issue.star_count, |
| 189 starred=services.issue_star.IsItemStarredBy( |
| 190 mar.cnxn, issue.issue_id, mar.auth.user_id), |
| 191 status=issue.status, |
| 192 state=(api_pb2_v1.IssueState.open if |
| 193 tracker_helpers.MeansOpenInProject( |
| 194 tracker_bizobj.GetStatus(issue), config) |
| 195 else api_pb2_v1.IssueState.closed), |
| 196 labels=issue.labels, |
| 197 components=component_list, |
| 198 author=convert_person(issue.reporter_id, mar.cnxn, services), |
| 199 owner=convert_person(issue.owner_id, mar.cnxn, services), |
| 200 cc=cc_list, |
| 201 updated=datetime.datetime.fromtimestamp(issue.modified_timestamp), |
| 202 published=datetime.datetime.fromtimestamp(issue.opened_timestamp), |
| 203 blockedOn=convert_issue_ids(issue.blocked_on_iids, mar, services), |
| 204 blocking=convert_issue_ids(issue.blocking_iids, mar, services), |
| 205 canComment=permissions.CanCommentIssue( |
| 206 mar.auth.effective_ids, mar.perms, issue_project, issue, |
| 207 granted_perms=granted_perms), |
| 208 canEdit=permissions.CanEditIssue( |
| 209 mar.auth.effective_ids, mar.perms, issue_project, issue, |
| 210 granted_perms=granted_perms), |
| 211 fieldValues=field_values_list) |
| 212 if issue.closed_timestamp > 0: |
| 213 resp.closed = datetime.datetime.fromtimestamp(issue.closed_timestamp) |
| 214 if issue.merged_into: |
| 215 resp.mergedInto=convert_issue_ids([issue.merged_into], mar, services)[0] |
| 216 return resp |
| 217 |
| 218 |
| 219 def convert_comment(issue, comment, mar, services, granted_perms): |
| 220 """Convert Monorail IssueComment PB to API IssueCommentWrapper.""" |
| 221 |
| 222 can_delete = permissions.CanDelete( |
| 223 mar.auth.user_id, mar.auth.effective_ids, mar.perms, |
| 224 comment.deleted_by, comment.user_id, mar.project, |
| 225 permissions.GetRestrictions(issue), granted_perms=granted_perms) |
| 226 |
| 227 return api_pb2_v1.IssueCommentWrapper( |
| 228 attachments=[convert_attachment(a) for a in comment.attachments], |
| 229 author=convert_person(comment.user_id, mar.cnxn, services, |
| 230 trap_exception=True), |
| 231 canDelete=can_delete, |
| 232 content=comment.content, |
| 233 deletedBy=convert_person(comment.deleted_by, mar.cnxn, services, |
| 234 trap_exception=True), |
| 235 id=comment.sequence, |
| 236 published=datetime.datetime.fromtimestamp(comment.timestamp), |
| 237 updates=convert_amendments(issue, comment.amendments, mar, services), |
| 238 kind='monorail#issueComment') |
| 239 |
| 240 |
| 241 def convert_attachment(attachment): |
| 242 """Convert Monorail Attachment PB to API Attachment.""" |
| 243 |
| 244 return api_pb2_v1.Attachment( |
| 245 attachmentId=attachment.attachment_id, |
| 246 fileName=attachment.filename, |
| 247 fileSize=attachment.filesize, |
| 248 mimetype=attachment.mimetype, |
| 249 isDeleted=attachment.deleted) |
| 250 |
| 251 |
| 252 def convert_amendments(issue, amendments, mar, services): |
| 253 """Convert a list of Monorail Amendment PBs to API Update.""" |
| 254 |
| 255 result = api_pb2_v1.Update(kind='monorail#issueCommentUpdate') |
| 256 for amendment in amendments: |
| 257 if amendment.field == tracker_pb2.FieldID.SUMMARY: |
| 258 result.summary = amendment.newvalue |
| 259 elif amendment.field == tracker_pb2.FieldID.STATUS: |
| 260 result.status = amendment.newvalue |
| 261 elif amendment.field == tracker_pb2.FieldID.OWNER: |
| 262 if len(amendment.added_user_ids) == 0: |
| 263 result.owner = framework_constants.NO_USER_NAME |
| 264 else: |
| 265 user_email = services.user.LookupUserEmail( |
| 266 mar.cnxn, amendment.added_user_ids[0]) |
| 267 result.owner = user_email |
| 268 elif amendment.field == tracker_pb2.FieldID.LABELS: |
| 269 result.labels = amendment.newvalue.split() |
| 270 elif amendment.field == tracker_pb2.FieldID.CC: |
| 271 for user_id in amendment.added_user_ids: |
| 272 user_email = services.user.LookupUserEmail(mar.cnxn, user_id) |
| 273 result.cc.append(user_email) |
| 274 for user_id in amendment.removed_user_ids: |
| 275 user_email = services.user.LookupUserEmail(mar.cnxn, user_id) |
| 276 result.cc.append('-%s' % user_email) |
| 277 elif amendment.field == tracker_pb2.FieldID.BLOCKEDON: |
| 278 result.blockedOn = _append_project( |
| 279 amendment.newvalue, issue.project_name) |
| 280 elif amendment.field == tracker_pb2.FieldID.BLOCKING: |
| 281 result.blocking = _append_project( |
| 282 amendment.newvalue, issue.project_name) |
| 283 elif amendment.field == tracker_pb2.FieldID.MERGEDINTO: |
| 284 result.mergedInto = amendment.newvalue |
| 285 elif amendment.field == tracker_pb2.FieldID.COMPONENTS: |
| 286 result.components = amendment.newvalue.split() |
| 287 elif amendment.field == tracker_pb2.FieldID.CUSTOM: |
| 288 fv = api_pb2_v1.FieldValue() |
| 289 fv.fieldName = amendment.custom_field_name |
| 290 fv.fieldValue = amendment.newvalue |
| 291 result.fieldValues.append(fv) |
| 292 |
| 293 return result |
| 294 |
| 295 |
| 296 def _append_project(issue_ids, project_name): |
| 297 """Append project name to convert <id> to <project>:<id> format.""" |
| 298 |
| 299 result = [] |
| 300 id_list = issue_ids.split() |
| 301 for id_str in id_list: |
| 302 if ':' in id_str: |
| 303 result.append(id_str) |
| 304 # '-' means this issue is being removed |
| 305 elif id_str.startswith('-'): |
| 306 result.append('-%s:%s' % (project_name, id_str[1:])) |
| 307 else: |
| 308 result.append('%s:%s' % (project_name, id_str)) |
| 309 return result |
| 310 |
| 311 |
| 312 def split_remove_add(item_list): |
| 313 """Split one list of items into two: items to add and items to remove.""" |
| 314 |
| 315 list_to_add = [] |
| 316 list_to_remove = [] |
| 317 |
| 318 for item in item_list: |
| 319 if item.startswith('-'): |
| 320 list_to_remove.append(item[1:]) |
| 321 else: |
| 322 list_to_add.append(item) |
| 323 |
| 324 return list_to_add, list_to_remove |
| 325 |
| 326 |
| 327 # TODO(sheyang): batch the SQL queries to fetch projects/issues. |
| 328 def issue_global_ids(project_local_id_pairs, project_id, mar, services): |
| 329 """Find global issues ids given <project_name>:<issue_local_id> pairs.""" |
| 330 |
| 331 result = [] |
| 332 for pair in project_local_id_pairs: |
| 333 issue_project_id = None |
| 334 local_id = None |
| 335 if ':' in pair: |
| 336 pair_ary = pair.split(':') |
| 337 project_name = pair_ary[0] |
| 338 local_id = int(pair_ary[1]) |
| 339 project = services.project.GetProjectByName(mar.cnxn, project_name) |
| 340 if not project: |
| 341 raise project_svc.NoSuchProjectException( |
| 342 'Project %s does not exist' % project_name) |
| 343 issue_project_id = project.project_id |
| 344 else: |
| 345 issue_project_id = project_id |
| 346 local_id = int(pair) |
| 347 result.append( |
| 348 services.issue.LookupIssueID(mar.cnxn, issue_project_id, local_id)) |
| 349 |
| 350 return result |
| 351 |
| 352 |
| 353 def convert_group_settings(group_name, setting): |
| 354 """Convert UserGroupSettings to UserGroupSettingsWrapper.""" |
| 355 return api_pb2_v1.UserGroupSettingsWrapper( |
| 356 groupName=group_name, |
| 357 who_can_view_members=setting.who_can_view_members, |
| 358 ext_group_type=setting.ext_group_type, |
| 359 last_sync_time=setting.last_sync_time) |
| 360 |
| 361 |
| 362 def convert_component_def(cd, mar, services): |
| 363 """Convert ComponentDef PB to Component PB.""" |
| 364 project_name = services.project.LookupProjectNames( |
| 365 mar.cnxn, [cd.project_id])[cd.project_id] |
| 366 user_ids = set() |
| 367 user_ids.update( |
| 368 cd.admin_ids + cd.cc_ids + [cd.creator_id] + [cd.modifier_id]) |
| 369 user_names_dict = services.user.LookupUserEmails(mar.cnxn, list(user_ids)) |
| 370 component = api_pb2_v1.Component( |
| 371 componentId=cd.component_id, |
| 372 projectName=project_name, |
| 373 componentPath=cd.path, |
| 374 description=cd.docstring, |
| 375 admin=sorted([user_names_dict[uid] for uid in cd.admin_ids]), |
| 376 cc=sorted([user_names_dict[uid] for uid in cd.cc_ids]), |
| 377 deprecated=cd.deprecated) |
| 378 if cd.created: |
| 379 component.created = datetime.datetime.fromtimestamp(cd.created) |
| 380 component.creator = user_names_dict[cd.creator_id] |
| 381 if cd.modified: |
| 382 component.modified = datetime.datetime.fromtimestamp(cd.modified) |
| 383 component.modifier = user_names_dict[cd.modifier_id] |
| 384 return component |
| 385 |
| 386 |
| 387 def convert_component_ids(config, component_names): |
| 388 """Convert a list of component names to ids.""" |
| 389 component_names_lower = [name.lower() for name in component_names] |
| 390 result = [] |
| 391 for cd in config.component_defs: |
| 392 cpath = cd.path |
| 393 if cpath.lower() in component_names_lower: |
| 394 result.append(cd.component_id) |
| 395 return result |
| 396 |
| 397 |
| 398 def convert_field_values(field_values, mar, services): |
| 399 """Convert user passed in field value list to FieldValue PB, or labels.""" |
| 400 fv_list_add = [] |
| 401 fv_list_remove = [] |
| 402 fv_list_clear = [] |
| 403 label_list_add = [] |
| 404 label_list_remove = [] |
| 405 field_name_dict = { |
| 406 fd.field_name: fd for fd in mar.config.field_defs} |
| 407 |
| 408 for fv in field_values: |
| 409 field_def = field_name_dict.get(fv.fieldName) |
| 410 if not field_def: |
| 411 logging.warning('Custom field %s of does not exist', fv.fieldName) |
| 412 continue |
| 413 |
| 414 if fv.operator == api_pb2_v1.FieldValueOperator.clear: |
| 415 fv_list_clear.append(field_def.field_id) |
| 416 continue |
| 417 |
| 418 # Enum fields are stored as labels |
| 419 if field_def.field_type == tracker_pb2.FieldTypes.ENUM_TYPE: |
| 420 raw_val = '%s-%s' % (fv.fieldName, fv.fieldValue) |
| 421 if fv.operator == api_pb2_v1.FieldValueOperator.remove: |
| 422 label_list_remove.append(raw_val) |
| 423 elif fv.operator == api_pb2_v1.FieldValueOperator.add: |
| 424 label_list_add.append(raw_val) |
| 425 else: |
| 426 logging.warning('Unsupported field value operater %s', fv.operator) |
| 427 else: |
| 428 new_fv = tracker_pb2.FieldValue( |
| 429 field_id=field_def.field_id) |
| 430 if field_def.field_type == tracker_pb2.FieldTypes.USER_TYPE: |
| 431 try: |
| 432 new_fv.user_id = services.user.LookupUserID(mar.cnxn, fv.fieldValue) |
| 433 except user_svc.NoSuchUserException: |
| 434 new_fv.user_id = 0 |
| 435 elif field_def.field_type == tracker_pb2.FieldTypes.STR_TYPE: |
| 436 new_fv.str_value = fv.fieldValue |
| 437 elif field_def.field_type == tracker_pb2.FieldTypes.INT_TYPE: |
| 438 new_fv.int_value = int(fv.fieldValue) |
| 439 else: |
| 440 logging.warning( |
| 441 'Unsupported field value type %s', field_def.field_type) |
| 442 |
| 443 if fv.operator == api_pb2_v1.FieldValueOperator.remove: |
| 444 fv_list_remove.append(new_fv) |
| 445 elif fv.operator == api_pb2_v1.FieldValueOperator.add: |
| 446 fv_list_add.append(new_fv) |
| 447 else: |
| 448 logging.warning('Unsupported field value operater %s', fv.operator) |
| 449 |
| 450 return (fv_list_add, fv_list_remove, fv_list_clear, |
| 451 label_list_add, label_list_remove) |
OLD | NEW |