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 """Servlet to export a range of issues in JSON format. |
| 7 """ |
| 8 |
| 9 import logging |
| 10 import time |
| 11 |
| 12 from third_party import ezt |
| 13 |
| 14 from framework import permissions |
| 15 from framework import jsonfeed |
| 16 from framework import servlet |
| 17 from tracker import tracker_bizobj |
| 18 |
| 19 |
| 20 class IssueExport(servlet.Servlet): |
| 21 """IssueExportControls let's an admin choose how to export issues.""" |
| 22 |
| 23 _PAGE_TEMPLATE = 'tracker/issue-export-page.ezt' |
| 24 _MAIN_TAB_MODE = servlet.Servlet.MAIN_TAB_ISSUES |
| 25 |
| 26 def AssertBasePermission(self, mr): |
| 27 """Make sure that the logged in user has permission to view this page.""" |
| 28 super(IssueExport, self).AssertBasePermission(mr) |
| 29 if not mr.auth.user_pb.is_site_admin: |
| 30 raise permissions.PermissionException( |
| 31 'Only site admins may export issues') |
| 32 |
| 33 def GatherPageData(self, mr): |
| 34 """Build up a dictionary of data values to use when rendering the page.""" |
| 35 |
| 36 return { |
| 37 'issue_tab_mode': None, |
| 38 'initial_start': mr.start, |
| 39 'initial_num': mr.num, |
| 40 'page_perms': self.MakePagePerms(mr, None, permissions.CREATE_ISSUE), |
| 41 } |
| 42 |
| 43 |
| 44 class IssueExportJSON(jsonfeed.JsonFeed): |
| 45 """IssueExport shows a range of issues in JSON format.""" |
| 46 |
| 47 # Pretty-print the JSON output. |
| 48 JSON_INDENT = 4 |
| 49 |
| 50 def AssertBasePermission(self, mr): |
| 51 """Make sure that the logged in user has permission to view this page.""" |
| 52 super(IssueExportJSON, self).AssertBasePermission(mr) |
| 53 if not mr.auth.user_pb.is_site_admin: |
| 54 raise permissions.PermissionException( |
| 55 'Only site admins may export issues') |
| 56 |
| 57 def HandleRequest(self, mr): |
| 58 """Build up a dictionary of data values to use when rendering the page. |
| 59 |
| 60 Args: |
| 61 mr: commonly used info parsed from the request. |
| 62 |
| 63 Returns: |
| 64 Dict of values used by EZT for rendering the page. |
| 65 """ |
| 66 if not mr.start and not mr.num: |
| 67 issues = self.services.issue.GetAllIssuesInProject( |
| 68 mr.cnxn, mr.project.project_id) |
| 69 else: |
| 70 local_id_range = range(mr.start, mr.start + mr.num) |
| 71 issues = self.services.issue.GetIssuesByLocalIDs( |
| 72 mr.cnxn, mr.project.project_id, local_id_range) |
| 73 user_id_set = tracker_bizobj.UsersInvolvedInIssues(issues) |
| 74 |
| 75 comments_dict = self.services.issue.GetCommentsForIssues( |
| 76 mr.cnxn, [issue.issue_id for issue in issues]) |
| 77 for comment_list in comments_dict.itervalues(): |
| 78 user_id_set.update( |
| 79 tracker_bizobj.UsersInvolvedInCommentList(comment_list)) |
| 80 |
| 81 starrers_dict = self.services.issue_star.LookupItemsStarrers( |
| 82 mr.cnxn, [issue.issue_id for issue in issues]) |
| 83 for starrer_id_list in starrers_dict.itervalues(): |
| 84 user_id_set.update(starrer_id_list) |
| 85 |
| 86 # The value 0 indicates "no user", e.g., that an issue has no owner. |
| 87 # We don't need to create a User row to represent that. |
| 88 user_id_set.discard(0) |
| 89 email_dict = self.services.user.LookupUserEmails(mr.cnxn, user_id_set) |
| 90 |
| 91 issues_json = [ |
| 92 self._MakeIssueJSON( |
| 93 mr, issue, email_dict, |
| 94 comments_dict.get(issue.issue_id, []), |
| 95 starrers_dict.get(issue.issue_id, [])) |
| 96 for issue in issues if not issue.deleted] |
| 97 |
| 98 json_data = { |
| 99 'metadata': { |
| 100 'version': 1, |
| 101 'when': int(time.time()), |
| 102 'who': mr.auth.email, |
| 103 'project': mr.project_name, |
| 104 'start': mr.start, |
| 105 'num': mr.num, |
| 106 }, |
| 107 'issues': issues_json, |
| 108 # This list could be derived from the 'issues', but we provide it for |
| 109 # ease of processing. |
| 110 'emails': email_dict.values(), |
| 111 } |
| 112 return json_data |
| 113 |
| 114 def _MakeAmendmentJSON(self, amendment, email_dict): |
| 115 amendment_json = { |
| 116 'field': amendment.field.name, |
| 117 } |
| 118 if amendment.custom_field_name: |
| 119 amendment_json.update({'custom_field_name': amendment.custom_field_name}) |
| 120 if amendment.newvalue: |
| 121 amendment_json.update({'new_value': amendment.newvalue}) |
| 122 if amendment.added_user_ids: |
| 123 amendment_json.update( |
| 124 {'added_emails': [email_dict.get(user_id) |
| 125 for user_id in amendment.added_user_ids]}) |
| 126 if amendment.removed_user_ids: |
| 127 amendment_json.update( |
| 128 {'removed_emails': [email_dict.get(user_id) |
| 129 for user_id in amendment.removed_user_ids]}) |
| 130 return amendment_json |
| 131 |
| 132 def _MakeAttachmentJSON(self, attachment): |
| 133 if attachment.deleted: |
| 134 return None |
| 135 attachment_json = { |
| 136 'name': attachment.filename, |
| 137 'size': attachment.filesize, |
| 138 'mimetype': attachment.mimetype, |
| 139 'gcs_object_id': attachment.gcs_object_id, |
| 140 } |
| 141 return attachment_json |
| 142 |
| 143 def _MakeCommentJSON(self, comment, email_dict): |
| 144 if comment.deleted_by: |
| 145 return None |
| 146 amendments = [self._MakeAmendmentJSON(a, email_dict) |
| 147 for a in comment.amendments] |
| 148 attachments = [self._MakeAttachmentJSON(a) |
| 149 for a in comment.attachments] |
| 150 comment_json = { |
| 151 'timestamp': comment.timestamp, |
| 152 'commenter': email_dict.get(comment.user_id), |
| 153 'content': comment.content, |
| 154 'amendments': [a for a in amendments if a], |
| 155 'attachments': [a for a in attachments if a], |
| 156 } |
| 157 return comment_json |
| 158 |
| 159 def _MakeFieldValueJSON(self, mr, field, email_dict): |
| 160 field_value_json = { |
| 161 'field': self.services.config.LookupField( |
| 162 mr.cnxn, mr.project.project_id, field.field_id) |
| 163 } |
| 164 if field.int_value: |
| 165 field_value_json['int_value'] = field.int_value |
| 166 if field.str_value: |
| 167 field_value_json['str_value'] = field.str_value |
| 168 if field.user_id: |
| 169 field_value_json['user_value'] = email_dict.get(field.user_id) |
| 170 return field_value_json |
| 171 |
| 172 def _MakeIssueJSON( |
| 173 self, mr, issue, email_dict, comment_list, starrer_id_list): |
| 174 """Return a dict of info about the issue and its comments.""" |
| 175 comments = [self._MakeCommentJSON(c, email_dict) for c in comment_list] |
| 176 issue_json = { |
| 177 'local_id': issue.local_id, |
| 178 'reporter': email_dict.get(issue.reporter_id), |
| 179 'summary': issue.summary, |
| 180 'owner': email_dict.get(issue.owner_id), |
| 181 'status': issue.status, |
| 182 'cc': [email_dict[cc_id] for cc_id in issue.cc_ids], |
| 183 'labels': issue.labels, |
| 184 'fields': [self._MakeFieldValueJSON(mr, field, email_dict) |
| 185 for field in issue.field_values], |
| 186 'starrers': [email_dict[starrer] for starrer in starrer_id_list], |
| 187 'comments': [c for c in comments if c], |
| 188 'opened': issue.opened_timestamp, |
| 189 'modified': issue.modified_timestamp, |
| 190 'closed': issue.closed_timestamp, |
| 191 } |
| 192 # TODO(agable): Export cross-project references as well. |
| 193 if issue.blocked_on_iids: |
| 194 issue_json['blocked_on'] = [i.local_id for i in |
| 195 self.services.issue.GetIssues(mr.cnxn, issue.blocked_on_iids) |
| 196 if i.project_id == mr.project.project_id] |
| 197 if issue.blocking_iids: |
| 198 issue_json['blocking'] = [i.local_id for i in |
| 199 self.services.issue.GetIssues(mr.cnxn, issue.blocking_iids) |
| 200 if i.project_id == mr.project.project_id] |
| 201 if issue.merged_into: |
| 202 merge = self.services.issue.GetIssue(mr.cnxn, issue.merged_into) |
| 203 if merge.project_id == mr.project.project_id: |
| 204 issue_json['merged_into'] = merge.local_id |
| 205 return issue_json |
OLD | NEW |