Merge lp:~brian-murray/errors/search-by-sourcepackage into lp:errors

Proposed by Brian Murray
Status: Merged
Merged at revision: 243
Proposed branch: lp:~brian-murray/errors/search-by-sourcepackage
Merge into: lp:errors
Diff against target: 300 lines (+118/-60)
3 files modified
api/resources.py (+51/-37)
launchpad.py (+66/-22)
static/js/most_common_problems.js (+1/-1)
To merge this branch: bzr merge lp:~brian-murray/errors/search-by-sourcepackage
Reviewer Review Type Date Requested Status
Evan (community) Approve
Review via email: mp+140306@code.launchpad.net

Description of the change

I've set it up so that a check is made to see if package is a source package and if it is then the binary packages making up that source package are found which are then used to find problems pertaining to all of those packages. I tested this in juju and it worked well.

I also did some python cleanup of the launchpad.py code.

The thing most needing review is the change to json_request in launchpad.py. I changed it so that now in the event of a KeyError the result of json.loads(content) is returned. This was necessary as 'ubuntu/current_series' does not return entries but a dictionary (with information about the current series) instead.

To post a comment you must log in.
Revision history for this message
Brian Murray (brian-murray) wrote :

I also wonder if it might be a good idea to modify the 'Package' column label in the most common problems table to be 'Binary Package'.

Revision history for this message
Evan (ev) wrote :

Yes, I think changing it to 'Binary package' is a good idea.

My general concern with changing the column headers is that we're already tight on space. If the header gets longer than the value for any of the rows, then we're reducing the amount of the signature column that can be read. Since compizconfig-settings-manager exists, I think we'll be okay in this instance.

Revision history for this message
Evan (ev) wrote :

This mostly looks good.

The rank will start at 1 for the most common problems in each package, which will look confusing when displayed in the table. You'll have repeating number for each binary package.

Instead, we should do a sorted insertion (to results) by count in and apply the rankings.

review: Needs Fixing
244. By Brian Murray

for the most common problems set the rank after adding results for all the packages

Revision history for this message
Brian Murray (brian-murray) wrote :

I've updated the branch so that a list of counts is kept and sorted. This is then used to determine the rank of each result. This has the effect of creating a ranking like [1, 1, 3, 3, 3, 6] which I think is actually more accurate as the items have the same count and should not have a ranking indicating one is "higher" than another when they are not.

Revision history for this message
Evan (ev) wrote :

Generally I would agree, but in this case "rank" is used to allow teams talking over the phone to identify a specific issue without a lot of added complexity. For example, "Brian, can you take issue #4 off the top crashes for software-center."

https://bugs.launchpad.net/errors/+bug/1046277

245. By Brian Murray

in the most common problems create a rank by sorting count and then function name

246. By Brian Murray

in the most common problems include the package name when the determining the rank

Revision history for this message
Brian Murray (brian-murray) wrote :

I've modified the branch so that rank is determined using frequency, then package name and then the function. I added in the package name as I found it odd to have /usr/sbin/cups appear after /usr/bin/usb-creator-gtk.

247. By Brian Murray

most_common_problems.js: set column label to binary package

Revision history for this message
Evan (ev) wrote :

I've merged this in with some fixes. Thanks for the hard work on this!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'api/resources.py'
2--- api/resources.py 2012-10-24 15:59:24 +0000
3+++ api/resources.py 2013-01-08 23:19:21 +0000
4@@ -142,44 +142,58 @@
5 period = request.GET.get('period', None)
6 from_date = str(request.GET.get('from', '')).translate(None, '/-')
7 to_date = str(request.GET.get('to', '')).translate(None, '/-')
8-
9- buckets = cassandra.get_bucket_counts(release, package,
10- version, period,
11- show_failed=True,
12- from_date=from_date,
13- to_date=to_date)
14-
15- # Since fetching the metadata is an expensive operation as it involves
16- # seeking the disk and searching other nodes for multiple rows, we
17- # filter the set of buckets we're concerned about first.
18- buckets = buckets[start:finish]
19 results = []
20- metadata = cassandra.get_metadata_for_buckets([x[0] for x in buckets], release)
21- i = 1
22- for bucket, count in buckets:
23- try:
24- m = metadata[bucket]
25- except KeyError:
26- m = {}
27- package = m.get('Source', '')
28- if not package:
29- # Because we weren't always writing the Source field in
30- # BucketMetadata, there are going to be a few buckets with an
31- # empty value here. Use the old method for looking up the
32- # package for now.
33- package, version = cassandra.get_package_for_bucket(bucket)
34-
35- last_seen = m.get('LastSeen', '')
36- report = m.get('CreatedBug', '') or m.get('LaunchpadBug', '')
37- results.append(ResultObject({
38- 'rank': i,
39- 'count': count,
40- 'package': package.decode('utf-8'),
41- 'first_seen' : m.get('FirstSeen', ''),
42- 'last_seen' : last_seen,
43- 'function': bucket.decode('utf-8'),
44- 'report': report }))
45- i += 1
46+ packages = []
47+ # XXX: is returning source package info if package is a binary package
48+ # the best?
49+ if package and launchpad.is_source_package(package):
50+ for binary in launchpad.get_binaries_in_source_package(package):
51+ packages.append(binary)
52+ else:
53+ packages.append(package)
54+ counts = []
55+ for package in packages:
56+ buckets = cassandra.get_bucket_counts(release, package,
57+ version, period,
58+ show_failed=True,
59+ from_date=from_date,
60+ to_date=to_date)
61+
62+ # Since fetching the metadata is an expensive operation as it involves
63+ # seeking the disk and searching other nodes for multiple rows, we
64+ # filter the set of buckets we're concerned about first.
65+ buckets = buckets[start:finish]
66+ metadata = cassandra.get_metadata_for_buckets([x[0] for x in buckets], release)
67+ for bucket, count in buckets:
68+ try:
69+ m = metadata[bucket]
70+ except KeyError:
71+ m = {}
72+ package = m.get('Source', '')
73+ if not package:
74+ # Because we weren't always writing the Source field in
75+ # BucketMetadata, there are going to be a few buckets with an
76+ # empty value here. Use the old method for looking up the
77+ # package for now.
78+ package, version = cassandra.get_package_for_bucket(bucket)
79+
80+ last_seen = m.get('LastSeen', '')
81+ report = m.get('CreatedBug', '') or m.get('LaunchpadBug', '')
82+ results.append(ResultObject({
83+ 'rank': 0,
84+ 'count': count,
85+ 'package': package.decode('utf-8'),
86+ 'first_seen': m.get('FirstSeen', ''),
87+ 'last_seen': last_seen,
88+ 'function': bucket.decode('utf-8'),
89+ 'report': report}))
90+ counts.append((count, package.decode('utf-8'),
91+ bucket.decode('utf-8')))
92+
93+ counts.sort(key=lambda t: (-int(t[0]), t[1], t[2]))
94+ for result in results:
95+ result.rank = counts.index((result.count, result.package,
96+ result.function)) + 1
97
98 return results
99
100
101=== modified file 'launchpad.py'
102--- launchpad.py 2012-10-18 13:59:42 +0000
103+++ launchpad.py 2013-01-08 23:19:21 +0000
104@@ -1,12 +1,12 @@
105-from oauth import oauth
106+import apt
107+import httplib2
108+import json
109+import sys
110 import urllib
111 import urllib2
112-import httplib2
113-import json
114-import apt
115-import lazr.uri
116+
117 from lazr.restfulclient._browser import AtomicFileCache
118-import sys
119+from oauth import oauth
120
121 configuration = None
122 try:
123@@ -59,7 +59,8 @@
124 _file_cache = AtomicFileCache(configuration.http_cache_dir)
125 _http = httplib2.Http(_file_cache)
126
127-def json_request (url):
128+
129+def json_request(url):
130 try:
131 response, content = _http.request(url)
132 except httplib2.ServerNotFoundError:
133@@ -67,21 +68,32 @@
134
135 try:
136 return json.loads(content)['entries']
137- except (ValueError, KeyError):
138+ except KeyError:
139+ # current_series has no entries
140+ return json.loads(content)
141+ except ValueError:
142 return ''
143
144-def get_all_codenames ():
145- url = _launchpad_base + '/ubuntu/series'
146- return [entry['name'] for entry in json_request (url)]
147-
148-def get_codename_for_version (version):
149- url = _launchpad_base + '/ubuntu/series'
150- for entry in json_request (url):
151+
152+def get_all_codenames():
153+ url = _launchpad_base + '/ubuntu/series'
154+ return [entry['name'] for entry in json_request(url)]
155+
156+
157+def get_codename_for_version(version):
158+ url = _launchpad_base + '/ubuntu/series'
159+ for entry in json_request(url):
160 if 'name' in entry and entry.get('version', None) == version:
161 return entry['name']
162 return None
163
164
165+def get_devel_series_codename():
166+ url = _launchpad_base + '/ubuntu/current_series'
167+ current_series = json_request(url)
168+ return current_series['name']
169+
170+
171 def get_version_for_codename(codename):
172 url = _launchpad_base + '/ubuntu/series'
173 for entry in json_request(url):
174@@ -90,7 +102,7 @@
175 return None
176
177
178-def get_versions_for_binary (binary_package, ubuntu_version):
179+def get_versions_for_binary(binary_package, ubuntu_version):
180 if not ubuntu_version:
181 codenames = get_all_codenames()
182 else:
183@@ -112,7 +124,8 @@
184 results |= set([get_version_for_codename(x['display_name'].split(' ')[3]) for x in json_request(url)])
185 return results
186
187-def binaries_are_most_recent (specific_packages, release=None):
188+
189+def binaries_are_most_recent(specific_packages, release=None):
190 '''For each (package, version) tuple supplied, determine if that is the
191 most recent version of the binary package.
192
193@@ -142,7 +155,8 @@
194 result.append(True)
195 return result
196
197-def _get_most_recent_binary_version (package, release):
198+
199+def _get_most_recent_binary_version(package, release):
200 url = _get_published_binaries_url % urllib.quote(package)
201 if release:
202 # TODO cache this by pushing it into the above function and instead
203@@ -155,7 +169,8 @@
204 except (KeyError, IndexError):
205 return ''
206
207-def binary_is_most_recent (package, version):
208+
209+def binary_is_most_recent(package, version):
210 # FIXME we need to factor in the release, otherwise this is often going to
211 # look like the issue has disappeared when filtering the most common
212 # problems view to a since-passed release.
213@@ -167,6 +182,7 @@
214 # we haven't seen it in the new version it may be fixed.
215 return apt.apt_pkg.version_compare(version, latest_version) != -1
216
217+
218 def bug_is_fixed(bug, release=None):
219 url = _get_bug_tasks_url % urllib.quote(bug)
220 if release:
221@@ -205,8 +221,32 @@
222 except (ValueError, KeyError):
223 return False
224
225+
226+def is_source_package(package_name):
227+ dev_series = get_devel_series_codename()
228+ url = _launchpad_base + '/ubuntu/' + dev_series + \
229+ ('/?ws.op=getSourcePackage&name=%s' % package_name)
230+ request = json_request(url)
231+ if request:
232+ return True
233+
234+
235+def get_binaries_in_source_package(package_name):
236+ dev_series = get_devel_series_codename()
237+ ma_url = _launchpad_base + '/ubuntu/' + dev_series + \
238+ '/main_archive'
239+ ma = json_request(ma_url)['self_link']
240+ ps_url = ma + ('/?ws.op=getPublishedSources&exact_match=true&source_name=%s'
241+ % package_name)
242+ # just use the first one, since they are unordered
243+ ps = json_request(ps_url)[0]['self_link']
244+ pb_url = ps + '/?ws.op=getPublishedBinaries'
245+ pbs = set(pb['binary_package_name'] for pb in json_request(pb_url))
246+ return pbs
247+
248 # Bug creation.
249
250+
251 def _generate_operation(title, description, target=_ubuntu_target):
252 _ubuntu_target = 'https://api.qastaging.launchpad.net/devel/ubuntu'
253
254@@ -216,6 +256,7 @@
255 'title' : title }
256 return urllib.urlencode(operation)
257
258+
259 def _generate_headers(oauth_token, oauth_secret):
260 a = (('OAuth realm="%s", '
261 'oauth_consumer_key="testing", '
262@@ -232,6 +273,7 @@
263 'Content-Type' : 'application/x-www-form-urlencoded' }
264 return headers
265
266+
267 def create_bug(signature, source=''):
268 '''Returns a tuple of (bug number, url)'''
269
270@@ -264,12 +306,14 @@
271 except KeyError:
272 return (None, None)
273
274+
275 def _generate_subscription(user):
276- operation = { 'ws.op' : 'subscribe',
277- 'level' : 'Discussion',
278- 'person' : _person_url + user }
279+ operation = {'ws.op': 'subscribe',
280+ 'level': 'Discussion',
281+ 'person': _person_url + user}
282 return urllib.urlencode(operation)
283
284+
285 def subscribe_user(bug, user):
286 operation = _generate_subscription(user)
287 headers = _generate_headers(configuration.lp_oauth_token,
288
289=== modified file 'static/js/most_common_problems.js'
290--- static/js/most_common_problems.js 2012-10-24 15:59:24 +0000
291+++ static/js/most_common_problems.js 2013-01-08 23:19:21 +0000
292@@ -173,7 +173,7 @@
293 var cols = [
294 {key: "rank", label: "Rank", sortable:true },
295 {key: "count", label: "Frequency", sortable:true, formatter:chartFormatter, allowHTML: true},
296- {key: "package", label: "Package", sortable:true, formatter:genericFormatter, allowHTML: true},
297+ {key: "package", label: "Binary Package", sortable:true, formatter:genericFormatter, allowHTML: true},
298 {key: "first_seen", label: "First seen", formatter:genericFormatter, allowHTML: true},
299 {key: "last_seen", label: "Last seen", formatter:genericFormatter, allowHTML: true},
300 {key: "function", label: "Function", formatter:functionFormatter, allowHTML: true},

Subscribers

People subscribed via source and target branches

to all changes: