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

Side by Side Diff: appengine/monorail/services/star_svc.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/services/spam_svc.py ('k') | appengine/monorail/services/test/__init__.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 """A set of functions that provide persistence for stars.
7
8 Stars can be on users, projects, or issues.
9 """
10
11 import logging
12
13 import settings
14 from features import filterrules_helpers
15 from framework import sql
16
17
18 USERSTAR_TABLE_NAME = 'UserStar'
19 PROJECTSTAR_TABLE_NAME = 'ProjectStar'
20 ISSUESTAR_TABLE_NAME = 'IssueStar'
21
22 # TODO(jrobbins): Consider adding memcache here if performance testing shows
23 # that stars are a bottleneck. Keep in mind that issue star counts are
24 # already denormalized and stored in the Issue, which is cached in memcache.
25
26
27 class AbstractStarService(object):
28 """The persistence layer for any kind of star data."""
29
30 def __init__(self, cache_manager, tbl, item_col, user_col, cache_kind):
31 """Constructor.
32
33 Args:
34 cache_manager: local cache with distributed invalidation.
35 tbl: SQL table that stores star data.
36 item_col: string SQL column name that holds int item IDs.
37 user_col: string SQL column name that holds int user IDs
38 of the user who starred the item.
39 cache_kind: string saying the kind of RAM cache.
40 """
41 self.tbl = tbl
42 self.item_col = item_col
43 self.user_col = user_col
44
45 # Items starred by users, keyed by user who did the starring.
46 self.star_cache = cache_manager.MakeCache('user')
47 # Users that starred an item, keyed by item ID.
48 self.starrer_cache = cache_manager.MakeCache(cache_kind)
49 # Counts of the users that starred an item, keyed by item ID.
50 self.star_count_cache = cache_manager.MakeCache(cache_kind)
51
52 def ExpungeStars(self, cnxn, item_id):
53 """Wipes an item's stars from the system."""
54 self.tbl.Delete(cnxn, **{self.item_col: item_id})
55
56 def LookupItemStarrers(self, cnxn, item_id):
57 """Returns list of users having stars on the specified item."""
58 starrer_list_dict = self.LookupItemsStarrers(cnxn, [item_id])
59 return starrer_list_dict[item_id]
60
61 def LookupItemsStarrers(self, cnxn, items_ids):
62 """Returns {item_id: [uid, ...]} of users who starred these items."""
63 starrer_list_dict, missed_ids = self.starrer_cache.GetAll(items_ids)
64
65 if missed_ids:
66 rows = self.tbl.Select(
67 cnxn, cols=[self.item_col, self.user_col],
68 **{self.item_col: missed_ids})
69 # Ensure that every requested item_id has an entry so that even
70 # zero-star items get cached.
71 retrieved_starrers = {item_id: [] for item_id in missed_ids}
72 for item_id, starrer_id in rows:
73 retrieved_starrers[item_id].append(starrer_id)
74 starrer_list_dict.update(retrieved_starrers)
75 self.starrer_cache.CacheAll(retrieved_starrers)
76
77 return starrer_list_dict
78
79 def LookupStarredItemIDs(self, cnxn, starrer_user_id):
80 """Returns list of item IDs that were starred by the specified user."""
81 if not starrer_user_id:
82 return [] # Anon user cannot star anything.
83
84 cached_item_ids = self.star_cache.GetItem(starrer_user_id)
85 if cached_item_ids is not None:
86 return cached_item_ids
87
88 rows = self.tbl.Select(cnxn, cols=[self.item_col], user_id=starrer_user_id)
89 starred_ids = [row[0] for row in rows]
90 self.star_cache.CacheItem(starrer_user_id, starred_ids)
91 return starred_ids
92
93 def IsItemStarredBy(self, cnxn, item_id, starrer_user_id):
94 """Return True if the given issue is starred by the given user."""
95 starred_ids = self.LookupStarredItemIDs(cnxn, starrer_user_id)
96 return item_id in starred_ids
97
98 def CountItemStars(self, cnxn, item_id):
99 """Returns the number of stars on the specified item."""
100 count_dict = self.CountItemsStars(cnxn, [item_id])
101 return count_dict.get(item_id, 0)
102
103 def CountItemsStars(self, cnxn, item_ids):
104 """Get a dict {item_id: count} for the given items."""
105 item_count_dict, missed_ids = self.star_count_cache.GetAll(item_ids)
106
107 if missed_ids:
108 rows = self.tbl.Select(
109 cnxn, cols=[self.item_col, 'COUNT(%s)' % self.user_col],
110 group_by=[self.item_col],
111 **{self.item_col: missed_ids})
112 # Ensure that every requested item_id has an entry so that even
113 # zero-star items get cached.
114 retrieved_counts = {item_id: 0 for item_id in missed_ids}
115 retrieved_counts.update(rows)
116 item_count_dict.update(retrieved_counts)
117 self.star_count_cache.CacheAll(retrieved_counts)
118
119 return item_count_dict
120
121 def SetStar(self, cnxn, item_id, starrer_user_id, starred):
122 """Sets or unsets a star for the specified item and user."""
123 if starred:
124 self.tbl.InsertRow(
125 cnxn, ignore=True,
126 **{self.item_col: item_id, self.user_col: starrer_user_id})
127 else:
128 self.tbl.Delete(
129 cnxn, **{self.item_col: item_id, self.user_col: starrer_user_id})
130
131 self.star_cache.Invalidate(cnxn, starrer_user_id)
132 self.starrer_cache.Invalidate(cnxn, item_id)
133
134
135 class UserStarService(AbstractStarService):
136 """Star service for stars on users."""
137
138 def __init__(self, cache_manager):
139 tbl = sql.SQLTableManager(USERSTAR_TABLE_NAME)
140 super(UserStarService, self).__init__(
141 cache_manager, tbl, 'starred_user_id', 'user_id', 'user')
142
143
144 class ProjectStarService(AbstractStarService):
145 """Star service for stars on projects."""
146
147 def __init__(self, cache_manager):
148 tbl = sql.SQLTableManager(PROJECTSTAR_TABLE_NAME)
149 super(ProjectStarService, self).__init__(
150 cache_manager, tbl, 'project_id', 'user_id', 'project')
151
152
153 class IssueStarService(AbstractStarService):
154 """Star service for stars on issues."""
155
156 def __init__(self, cache_manager):
157 tbl = sql.SQLTableManager(ISSUESTAR_TABLE_NAME)
158 super(IssueStarService, self).__init__(
159 cache_manager, tbl, 'issue_id', 'user_id', 'issue')
160
161 # pylint: disable=arguments-differ
162 def SetStar(
163 self, cnxn, services, config, issue_id, starrer_user_id, starred):
164 # TODO(agable): The number of arguments required by this function is
165 # crazy. Find a way to simplify it so that it only needs the same
166 # arguments as AbstractSetStar above.
167 """Add or remove a star on the given issue for the given user.
168
169 Args:
170 cnxn: connection to SQL database.
171 services: connections to persistence layer.
172 config: ProjectIssueConfig PB for the project containing the issue.
173 issue_id: integer global ID of an issue.
174 starrer_user_id: user ID of the user who starred the issue.
175 starred: boolean True for adding a star, False when removing one.
176 """
177 logging.info(
178 'SetIssueStar:%06d, %s, %s', issue_id, starrer_user_id, starred)
179 super(IssueStarService, self).SetStar(
180 cnxn, issue_id, starrer_user_id, starred)
181
182 issue = services.issue.GetIssue(cnxn, issue_id)
183 issue.star_count = self.CountItemStars(cnxn, issue_id)
184 filterrules_helpers.ApplyFilterRules(cnxn, services, issue, config)
185 # Note: only star_count could change due to the starring, but any
186 # field could have changed as a result of filter rules.
187 services.issue.UpdateIssue(cnxn, issue)
188
189 self.star_cache.Invalidate(cnxn, starrer_user_id)
190 self.starrer_cache.Invalidate(cnxn, issue_id)
OLDNEW
« no previous file with comments | « appengine/monorail/services/spam_svc.py ('k') | appengine/monorail/services/test/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698