Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(46)

Side by Side Diff: appengine/monorail/features/activities.py

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « appengine/monorail/features/__init__.py ('k') | appengine/monorail/features/autolink.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 """Code to support project and user activies pages."""
7
8 import logging
9 import time
10
11 from third_party import ezt
12
13 from framework import framework_constants
14 from framework import framework_helpers
15 from framework import framework_views
16 from framework import sql
17 from framework import template_helpers
18 from framework import timestr
19 from project import project_views
20 from proto import tracker_pb2
21 from tracker import tracker_helpers
22 from tracker import tracker_views
23
24
25 UPDATES_PER_PAGE = 50
26 MAX_UPDATES_PER_PAGE = 200
27
28
29 class ActivityView(template_helpers.PBProxy):
30 """EZT-friendly wrapper for Activities."""
31
32 _TITLE_TEMPLATE = template_helpers.MonorailTemplate(
33 framework_constants.TEMPLATE_PATH + 'features/activity-title.ezt',
34 compress_whitespace=True, base_format=ezt.FORMAT_HTML)
35
36 _BODY_TEMPLATE = template_helpers.MonorailTemplate(
37 framework_constants.TEMPLATE_PATH + 'features/activity-body.ezt',
38 compress_whitespace=True, base_format=ezt.FORMAT_HTML)
39
40 def __init__(
41 self, pb, services, mr, prefetched_issues, users_by_id,
42 autolink=None, all_ref_artifacts=None, ending=None, highlight=None):
43 """Constructs an ActivityView out of an Activity protocol buffer.
44
45 Args:
46 pb: an IssueComment or Activity protocol buffer.
47 services: connections to backend services.
48 mr: HTTP request info, used by the artifact autolink.
49 prefetched_issues: dictionary of the issues for the comments being shown.
50 users_by_id: dict {user_id: UserView} for all relevant users.
51 autolink: Autolink instance.
52 all_ref_artifacts: list of all artifacts in the activity stream.
53 ending: ending type for activity titles, 'in_project' or 'by_user'
54 highlight: what to highlight in the middle column on user updates pages
55 i.e. 'project', 'user', or None
56 """
57 template_helpers.PBProxy.__init__(self, pb)
58
59 activity_type = 'ProjectIssueUpdate' # TODO(jrobbins): more types
60
61 self.comment = None
62 self.issue = None
63 self.field_changed = None
64 self.multiple_fields_changed = ezt.boolean(False)
65 self.project = None
66 self.user = None
67 self.timestamp = time.time() # Bogus value makes bad ones highly visible.
68
69 if isinstance(pb, tracker_pb2.IssueComment):
70 self.timestamp = pb.timestamp
71 issue = prefetched_issues[pb.issue_id]
72 if self.timestamp == issue.opened_timestamp:
73 issue_change_id = None # This comment is the description.
74 else:
75 issue_change_id = pb.id # instead of seq num.
76
77 self.comment = tracker_views.IssueCommentView(
78 mr.project_name, pb, users_by_id, autolink,
79 all_ref_artifacts, mr, issue)
80
81 # TODO(jrobbins): pass effective_ids of the commenter so that he/she
82 # can be identified as a project member or not.
83 # TODO(jrobbins): Prefetch all needed projects and configs just like the
84 # way that we batch-prefetch issues.
85 config = services.config.GetProjectConfig(mr.cnxn, issue.project_id)
86 self.issue = tracker_views.IssueView(issue, users_by_id, config)
87 self.user = self.comment.creator
88 project = services.project.GetProject(mr.cnxn, issue.project_id)
89 self.project_name = project.project_name
90 self.project = project_views.ProjectView(project)
91
92 else:
93 logging.warn('unknown activity object %r', pb)
94
95 nested_page_data = {
96 'activity_type': activity_type,
97 'issue_change_id': issue_change_id,
98 'comment': self.comment,
99 'issue': self.issue,
100 'project': self.project,
101 'user': self.user,
102 'timestamp': self.timestamp,
103 'ending_type': ending,
104 }
105
106 self.escaped_title = self._TITLE_TEMPLATE.GetResponse(
107 nested_page_data).strip()
108 self.escaped_body = self._BODY_TEMPLATE.GetResponse(
109 nested_page_data).strip()
110
111 if autolink is not None and all_ref_artifacts is not None:
112 # TODO(jrobbins): actually parse the comment text. Actually render runs.
113 runs = autolink.MarkupAutolinks(
114 mr, [template_helpers.TextRun(self.escaped_body)], all_ref_artifacts)
115 self.escaped_body = ''.join(run.content for run in runs)
116
117 self.date_bucket, self.date_relative = timestr.GetHumanScaleDate(
118 self.timestamp)
119 time_tuple = time.localtime(self.timestamp)
120 self.date_tooltip = time.asctime(time_tuple)
121
122 # We always highlight the user for starring activities
123 if activity_type.startswith('UserStar'):
124 self.highlight = 'user'
125 else:
126 self.highlight = highlight
127
128
129 def GatherUpdatesData(
130 services, mr, prof, project_ids=None, user_ids=None, ending=None,
131 updates_page_url=None, autolink=None, highlight=None):
132 """Gathers and returns updates data.
133
134 Args:
135 services: Connections to backend services.
136 mr: HTTP request info, used by the artifact autolink.
137 prof: The profiler to use.
138 project_ids: List of project IDs we want updates for.
139 user_ids: List of user IDs we want updates for.
140 ending: Ending type for activity titles, 'in_project' or 'by_user'.
141 updates_page_url: The URL that will be used to create pagination links from.
142 autolink: Autolink instance.
143 highlight: What to highlight in the middle column on user updates pages
144 i.e. 'project', 'user', or None.
145 """
146 ascending = bool(mr.after)
147
148 # num should be non-negative number
149 num = mr.GetPositiveIntParam('num', UPDATES_PER_PAGE)
150 num = min(num, MAX_UPDATES_PER_PAGE)
151
152 updates_data = {
153 'no_stars': None,
154 'no_activities': None,
155 'pagination': None,
156 'updates_data': None,
157 'ending_type': ending,
158 }
159
160 if not user_ids and not project_ids:
161 updates_data['no_stars'] = ezt.boolean(True)
162 return updates_data
163
164 with prof.Phase('get activities'):
165 # TODO(jrobbins): make this into a persist method.
166 # TODO(jrobbins): this really needs permission checking in SQL, which will
167 # be slow.
168 where_conds = [('Issue.id = Comment.issue_id', [])]
169 if project_ids is not None:
170 cond_str = 'Comment.project_id IN (%s)' % sql.PlaceHolders(project_ids)
171 where_conds.append((cond_str, project_ids))
172 if user_ids is not None:
173 cond_str = 'Comment.commenter_id IN (%s)' % sql.PlaceHolders(user_ids)
174 where_conds.append((cond_str, user_ids))
175
176 if project_ids:
177 use_clause = 'USE INDEX (project_id) USE INDEX FOR ORDER BY (project_id)'
178 elif user_ids:
179 use_clause = (
180 'USE INDEX (commenter_id) USE INDEX FOR ORDER BY (commenter_id)')
181 else:
182 use_clause = ''
183
184 if mr.before:
185 where_conds.append(('created < %s', [mr.before]))
186 if mr.after:
187 where_conds.append(('created > %s', [mr.after]))
188 if ascending:
189 order_by = [('created', [])]
190 else:
191 order_by = [('created DESC', [])]
192
193 comments = services.issue.GetComments(
194 mr.cnxn, joins=[('Issue', [])], deleted_by=None, where=where_conds,
195 use_clause=use_clause, order_by=order_by, limit=num + 1)
196
197 # TODO(jrobbins): it would be better if we could just get the dict directly.
198 prefetched_issues_list = services.issue.GetIssues(
199 mr.cnxn, {c.issue_id for c in comments})
200 prefetched_issues = {
201 issue.issue_id: issue for issue in prefetched_issues_list}
202 needed_project_ids = {issue.project_id for issue in prefetched_issues_list}
203 prefetched_projects = services.project.GetProjects(
204 mr.cnxn, needed_project_ids)
205 prefetched_configs = services.config.GetProjectConfigs(
206 mr.cnxn, needed_project_ids)
207 viewable_issues_list = tracker_helpers.FilterOutNonViewableIssues(
208 mr.auth.effective_ids, mr.auth.user_pb, prefetched_projects,
209 prefetched_configs, prefetched_issues_list)
210 viewable_iids = {issue.issue_id for issue in viewable_issues_list}
211
212 # Filter the comments based on permission to view the issue.
213 # TODO(jrobbins): push permission checking in the query so that pagination
214 # pages never become underfilled, or use backends to shard.
215 # TODO(jrobbins): come back to this when I implement private comments.
216 comments = [
217 c for c in comments if c.issue_id in viewable_iids]
218
219 if ascending:
220 comments.reverse()
221
222 amendment_user_ids = []
223 for comment in comments:
224 for amendment in comment.amendments:
225 amendment_user_ids.extend(amendment.added_user_ids)
226 amendment_user_ids.extend(amendment.removed_user_ids)
227
228 users_by_id = framework_views.MakeAllUserViews(
229 mr.cnxn, services.user, [c.user_id for c in comments],
230 amendment_user_ids)
231 framework_views.RevealAllEmailsToMembers(mr, users_by_id)
232
233 num_results_returned = len(comments)
234 displayed_activities = comments[:UPDATES_PER_PAGE]
235
236 if not num_results_returned:
237 updates_data['no_activities'] = ezt.boolean(True)
238 return updates_data
239
240 # Get all referenced artifacts first
241 all_ref_artifacts = None
242 if autolink is not None:
243 content_list = []
244 for activity in comments:
245 content_list.append(activity.content)
246
247 all_ref_artifacts = autolink.GetAllReferencedArtifacts(
248 mr, content_list)
249
250 # Now process content and gather activities
251 today = []
252 yesterday = []
253 pastweek = []
254 pastmonth = []
255 thisyear = []
256 older = []
257
258 with prof.Phase('rendering activities'):
259 for activity in displayed_activities:
260 entry = ActivityView(
261 activity, services, mr, prefetched_issues, users_by_id,
262 autolink=autolink, all_ref_artifacts=all_ref_artifacts, ending=ending,
263 highlight=highlight)
264
265 if entry.date_bucket == 'Today':
266 today.append(entry)
267 elif entry.date_bucket == 'Yesterday':
268 yesterday.append(entry)
269 elif entry.date_bucket == 'Last 7 days':
270 pastweek.append(entry)
271 elif entry.date_bucket == 'Last 30 days':
272 pastmonth.append(entry)
273 elif entry.date_bucket == 'Earlier this year':
274 thisyear.append(entry)
275 elif entry.date_bucket == 'Older':
276 older.append(entry)
277
278 new_after = None
279 new_before = None
280 if displayed_activities:
281 new_after = displayed_activities[0].timestamp
282 new_before = displayed_activities[-1].timestamp
283
284 prev_url = None
285 next_url = None
286 if updates_page_url:
287 list_servlet_rel_url = updates_page_url.split('/')[-1]
288 if displayed_activities and (mr.before or mr.after):
289 prev_url = framework_helpers.FormatURL(
290 mr, list_servlet_rel_url, after=new_after)
291 if mr.after or len(comments) > UPDATES_PER_PAGE:
292 next_url = framework_helpers.FormatURL(
293 mr, list_servlet_rel_url, before=new_before)
294
295 if prev_url or next_url:
296 pagination = template_helpers.EZTItem(
297 start=None, last=None, prev_url=prev_url, next_url=next_url,
298 reload_url=None, visible=ezt.boolean(True), total_count=None)
299 else:
300 pagination = None
301
302 updates_data.update({
303 'no_activities': ezt.boolean(False),
304 'pagination': pagination,
305 'updates_data': template_helpers.EZTItem(
306 today=today, yesterday=yesterday, pastweek=pastweek,
307 pastmonth=pastmonth, thisyear=thisyear, older=older),
308 })
309
310 return updates_data
OLDNEW
« no previous file with comments | « appengine/monorail/features/__init__.py ('k') | appengine/monorail/features/autolink.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698