Merge lp:~stevenk/launchpad/bugsubscriptionfilter-itype-ui into lp:launchpad

Proposed by Steve Kowalik on 2012-08-07
Status: Merged
Approved by: Steve Kowalik on 2012-08-07
Approved revision: no longer in the source branch.
Merged at revision: 15756
Proposed branch: lp:~stevenk/launchpad/bugsubscriptionfilter-itype-ui
Merge into: lp:launchpad
Diff against target: 552 lines (+127/-41)
5 files modified
lib/lp/bugs/browser/bugsubscriptionfilter.py (+16/-13)
lib/lp/bugs/browser/structuralsubscription.py (+5/-7)
lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py (+26/-11)
lib/lp/registry/javascript/structural-subscription.js (+51/-6)
lib/lp/registry/javascript/tests/test_structural_subscription.js (+29/-4)
To merge this branch: bzr merge lp:~stevenk/launchpad/bugsubscriptionfilter-itype-ui
Reviewer Review Type Date Requested Status
Ian Booth (community) 2012-08-07 Approve on 2012-08-07
Review via email: mp+118472@code.launchpad.net

Commit Message

Hook up the UI to allow people to filter structural subscriptions by the information type of a bug.

Description of the Change

Hook up the UI to allow people to filter structural subscriptions by the information type of a bug.

I have performed a little bit of drive-by, by fixing some whitespace where I noticed it, and changing a few functions, just trying to keep the LOC count down. And then the JS happened.

Screenshot at http://people.canonical.com/~stevenk/information_type_filter.jpg

To post a comment you must log in.
Ian Booth (wallyworld) wrote :

The screenshot looks nice. Thanks for making the little tweaks asked for.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugsubscriptionfilter.py'
2--- lib/lp/bugs/browser/bugsubscriptionfilter.py 2012-03-13 00:45:33 +0000
3+++ lib/lp/bugs/browser/bugsubscriptionfilter.py 2012-08-07 07:08:19 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2011 Canonical Ltd. This software is licensed under the
6+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """View classes for bug subscription filters."""
10@@ -70,6 +70,13 @@
11 # in particular, if no importances are checked, or no statuses.
12 filters_everything = False
13
14+ def _add_english_condition(self, conditions, variable, description):
15+ if len(variable) > 0:
16+ conditions.append(
17+ u"the %s is %s" % (description, english_list(
18+ (kind.title for kind in sorted(variable)),
19+ conjunction=u"or")))
20+
21 @property
22 def conditions(self):
23 """Descriptions of the bug subscription filter's conditions."""
24@@ -80,24 +87,18 @@
25 'the bug')
26 conditions.append(
27 mapping[bug_notification_level].lower()[:-1])
28- statuses = self.context.statuses
29- if len(statuses) > 0:
30- conditions.append(
31- u"the status is %s" % english_list(
32- (status.title for status in sorted(statuses)),
33- conjunction=u"or"))
34- importances = self.context.importances
35- if len(importances) > 0:
36- conditions.append(
37- u"the importance is %s" % english_list(
38- (importance.title for importance in sorted(importances)),
39- conjunction=u"or"))
40+ self._add_english_condition(
41+ conditions, self.context.statuses, 'status')
42+ self._add_english_condition(
43+ conditions, self.context.importances, 'importance')
44 tags = self.context.tags
45 if len(tags) > 0:
46 conditions.append(
47 u"the bug is tagged with %s" % english_list(
48 sorted(tags), conjunction=(
49 u"and" if self.context.find_all_tags else u"or")))
50+ self._add_english_condition(
51+ conditions, self.context.information_types, 'information type')
52 return conditions
53
54
55@@ -110,6 +111,7 @@
56 "description",
57 "statuses",
58 "importances",
59+ "information_types",
60 "tags",
61 "find_all_tags",
62 )
63@@ -117,6 +119,7 @@
64 custom_widget("description", TextWidget, displayWidth=50)
65 custom_widget("statuses", LabeledMultiCheckBoxWidget)
66 custom_widget("importances", LabeledMultiCheckBoxWidget)
67+ custom_widget("information_types", LabeledMultiCheckBoxWidget)
68 custom_widget("tags", BugTagsFrozenSetWidget, displayWidth=35)
69
70 # Define in concrete subclass to be the target of the
71
72=== modified file 'lib/lp/bugs/browser/structuralsubscription.py'
73--- lib/lp/bugs/browser/structuralsubscription.py 2012-03-18 01:29:07 +0000
74+++ lib/lp/bugs/browser/structuralsubscription.py 2012-08-07 07:08:19 +0000
75@@ -1,4 +1,4 @@
76-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
77+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
78 # GNU Affero General Public License version 3 (see the file LICENSE).
79
80 __metaclass__ = type
81@@ -52,6 +52,7 @@
82 IStructuralSubscriptionTarget,
83 IStructuralSubscriptionTargetHelper,
84 )
85+from lp.registry.enums import InformationType
86 from lp.registry.interfaces.distribution import IDistribution
87 from lp.registry.interfaces.distributionsourcepackage import (
88 IDistributionSourcePackage,
89@@ -210,8 +211,7 @@
90 Returns True is the user is subscribed to bug notifications
91 for the context target.
92 """
93- subscription = self.context.getSubscription(person)
94- return subscription is not None
95+ return self.context.getSubscription(person) is not None
96
97 def currentUserIsSubscribed(self):
98 """Return True, if the current user is subscribed."""
99@@ -415,6 +415,7 @@
100 expose_user_administered_teams_to_js(request, user, context)
101 expose_enum_to_js(request, BugTaskImportance, 'importances')
102 expose_enum_to_js(request, BugTaskStatus, 'statuses')
103+ expose_enum_to_js(request, InformationType, 'information_types')
104 if subscriptions is None:
105 try:
106 # No subscriptions, which means we are on a target
107@@ -432,10 +433,7 @@
108
109 def expose_enum_to_js(request, enum, name):
110 """Make a list of enum titles and value available to JavaScript."""
111- info = []
112- for item in enum:
113- info.append(item.title)
114- IJSONRequestCache(request).objects[name] = info
115+ IJSONRequestCache(request).objects[name] = [item.title for item in enum]
116
117
118 def expose_user_administered_teams_to_js(request, user, context,
119
120=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py'
121--- lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py 2012-03-13 00:45:33 +0000
122+++ lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py 2012-08-07 07:08:19 +0000
123@@ -1,4 +1,4 @@
124-# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
125+# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
126 # GNU Affero General Public License version 3 (see the file LICENSE).
127
128 """Tests for bug subscription filter browser code."""
129@@ -22,6 +22,7 @@
130 BugTaskImportance,
131 BugTaskStatus,
132 )
133+from lp.registry.enums import InformationType
134 from lp.services.webapp.publisher import canonical_url
135 from lp.services.webapp.servers import LaunchpadTestRequest
136 from lp.testing import (
137@@ -320,6 +321,17 @@
138 [u"the bug is tagged with *, bar, and foo"],
139 self.view.conditions)
140
141+ def test_conditions_for_information_types(self):
142+ # If no information types have been specified nothing is returned.
143+ self.assertEqual([], self.view.conditions)
144+ # If set, a description of the information type is returned.
145+ with person_logged_in(self.owner):
146+ self.subscription_filter.information_types = [
147+ InformationType.PRIVATESECURITY, InformationType.USERDATA]
148+ self.assertEqual(
149+ [u"the information type is Private Security or Private"],
150+ self.view.conditions)
151+
152 def assertRender(self, dt_content=None, dd_content=None):
153 root = html.fromstring(self.view.render())
154 if dt_content is not None:
155@@ -435,6 +447,7 @@
156 "field.description": "New description",
157 "field.statuses": ["NEW", "INCOMPLETE"],
158 "field.importances": ["LOW", "MEDIUM"],
159+ "field.information_types": ["USERDATA"],
160 "field.tags": u"foo bar",
161 "field.find_all_tags": "on",
162 "field.actions.update": "Update",
163@@ -445,8 +458,7 @@
164 self.assertEqual([], view.errors)
165 # The subscription filter has been updated.
166 self.assertEqual(
167- u"New description",
168- self.subscription_filter.description)
169+ u"New description", self.subscription_filter.description)
170 self.assertEqual(
171 frozenset([BugTaskStatus.NEW, BugTaskStatus.INCOMPLETE]),
172 self.subscription_filter.statuses)
173@@ -454,10 +466,11 @@
174 frozenset([BugTaskImportance.LOW, BugTaskImportance.MEDIUM]),
175 self.subscription_filter.importances)
176 self.assertEqual(
177- frozenset([u"foo", u"bar"]),
178- self.subscription_filter.tags)
179- self.assertTrue(
180- self.subscription_filter.find_all_tags)
181+ frozenset([InformationType.USERDATA]),
182+ self.subscription_filter.information_types)
183+ self.assertEqual(
184+ frozenset([u"foo", u"bar"]), self.subscription_filter.tags)
185+ self.assertTrue(self.subscription_filter.find_all_tags)
186
187 def test_delete(self):
188 # The filter can be deleted by using the delete action.
189@@ -551,6 +564,7 @@
190 "field.description": "New description",
191 "field.statuses": ["NEW", "INCOMPLETE"],
192 "field.importances": ["LOW", "MEDIUM"],
193+ "field.information_types": ["PRIVATESECURITY"],
194 "field.tags": u"foo bar",
195 "field.find_all_tags": "on",
196 "field.actions.create": "Create",
197@@ -573,7 +587,8 @@
198 frozenset([BugTaskImportance.LOW, BugTaskImportance.MEDIUM]),
199 subscription_filter.importances)
200 self.assertEqual(
201- frozenset([u"foo", u"bar"]),
202- subscription_filter.tags)
203- self.assertTrue(
204- subscription_filter.find_all_tags)
205+ frozenset([InformationType.PRIVATESECURITY]),
206+ subscription_filter.information_types)
207+ self.assertEqual(
208+ frozenset([u"foo", u"bar"]), subscription_filter.tags)
209+ self.assertTrue(subscription_filter.find_all_tags)
210
211=== modified file 'lib/lp/registry/javascript/structural-subscription.js'
212--- lib/lp/registry/javascript/structural-subscription.js 2012-07-20 22:48:23 +0000
213+++ lib/lp/registry/javascript/structural-subscription.js 2012-08-07 07:08:19 +0000
214@@ -1,4 +1,4 @@
215-/* Copyright 2011 Canonical Ltd. This software is licensed under the
216+/* Copyright 2011-2012 Canonical Ltd. This software is licensed under the
217 * GNU Affero General Public License version 3 (see the file LICENSE).
218 *
219 * Form overlay widgets and subscriber handling for structural subscriptions.
220@@ -103,7 +103,8 @@
221 tags: [],
222 find_all_tags: false,
223 importances: [],
224- statuses: []
225+ statuses: [],
226+ information_types: []
227 };
228
229 // Set the notification level.
230@@ -137,10 +138,14 @@
231 if (form_data.statuses.length > 0) {
232 patch_data.statuses = form_data.statuses;
233 }
234+ if (form_data.information_types.length > 0) {
235+ patch_data.information_types = form_data.information_types;
236+ }
237 } else {
238 // clear out the tags, statuses, and importances in case this is an
239 // edit.
240 patch_data.tags = patch_data.importances = patch_data.statuses = [];
241+ patch_data.information_types = [];
242 }
243 return patch_data;
244 }
245@@ -431,6 +436,8 @@
246 content_node, LP.cache.statuses, LP.cache.statuses);
247 set_checkboxes(
248 content_node, LP.cache.importances, LP.cache.importances);
249+ set_checkboxes(
250+ content_node, LP.cache.information_types, LP.cache.information_types);
251 content_node.one('[name="tags"]').set('value', '');
252 set_radio_buttons(
253 content_node, [MATCH_ALL, MATCH_ANY], MATCH_ALL);
254@@ -556,7 +563,8 @@
255
256 var statuses_ai,
257 importances_ai,
258- tags_ai;
259+ tags_ai,
260+ information_types_ai;
261
262 // Build tags pane.
263 tags_ai = new Y.AccordionItem( {
264@@ -644,6 +652,28 @@
265 var official_bug_tags = LP.cache.context.official_bug_tags || [];
266 namespace.bug_tag_completer = Y.lp.bugs.tags_entry.setup_tag_complete(
267 'input[name="tags"]', official_bug_tags);
268+
269+ // Build information_types pane.
270+ information_types_ai = new Y.AccordionItem( {
271+ label: "Information types",
272+ expanded: false,
273+ alwaysVisible: false,
274+ id: "information_types_ai",
275+ contentHeight: {method: "auto"}
276+ } );
277+ var information_types = LP.cache.information_types;
278+ selectors = make_selector_controls('information_types');
279+ information_types_ai.set("bodyContent",
280+ Y.Node.create('<div id="information_types-wrapper"></div>')
281+ .append(selectors.node)
282+ .append(make_table(information_types, 'information_types', 3)));
283+ accordion.addItem(information_types_ai);
284+ // Wire up the 'all' and 'none' selectors.
285+ node = content_node.one('#information_types-wrapper');
286+ selectors.all_link.on('click',
287+ make_select_handler(node, information_types, true));
288+ selectors.none_link.on('click',
289+ make_select_handler(node, information_types, false));
290 return accordion;
291 }
292
293@@ -1042,10 +1072,12 @@
294 function set_filter_statuses_and_importances(content_node, filter) {
295 var is_lifecycle = filter.bug_notification_level==='Lifecycle',
296 statuses = filter.statuses,
297- importances = filter.importances;
298+ importances = filter.importances,
299+ information_types = filter.information_types;
300 if (is_lifecycle) {
301 statuses = LP.cache.statuses;
302 importances = LP.cache.importances;
303+ information_types = LP.cache.information_types;
304 } else {
305 // An absence of values is equivalent to all values.
306 if (statuses.length === 0) {
307@@ -1054,10 +1086,15 @@
308 if (importances.length === 0) {
309 importances = LP.cache.importances;
310 }
311+ if (information_types.length === 0) {
312+ information_types = LP.cache.information_types;
313+ }
314 }
315 set_checkboxes(content_node, LP.cache.statuses, statuses);
316 set_checkboxes(
317 content_node, LP.cache.importances, importances);
318+ set_checkboxes(
319+ content_node, LP.cache.information_types, information_types);
320 }
321
322 /**
323@@ -1082,6 +1119,7 @@
324 has_advanced_filters = !is_lifecycle && (
325 filter.statuses.length ||
326 filter.importances.length ||
327+ filter.information_types.length ||
328 filter.tags.length) > 0,
329 filters = has_advanced_filters ? [ADVANCED_FILTER] : [],
330 event = ADDED_OR_CHANGED;
331@@ -1545,14 +1583,21 @@
332 // Format status conditions.
333 if (filter.statuses.length !== 0) {
334 filter_items.push(Y.Node.create('<li></li>')
335- .set('text', 'have the status(es): '+filter.statuses.join(', ')));
336+ .set('text', 'have the status(es): ' + filter.statuses.join(', ')));
337 }
338
339 // Format importance conditions.
340 if (filter.importances.length !== 0) {
341 filter_items.push(Y.Node.create('<li></li>')
342 .set('text',
343- 'are of importance: '+filter.importances.join(', ')));
344+ 'are of importance: ' + filter.importances.join(', ')));
345+ }
346+
347+ // Format information type conditions.
348+ if (filter.information_types.length !== 0) {
349+ filter_items.push(Y.Node.create('<li></li>')
350+ .set('text',
351+ 'are of information type: ' + filter.information_types.join(', ')));
352 }
353
354 // Format tag conditions.
355
356=== modified file 'lib/lp/registry/javascript/tests/test_structural_subscription.js'
357--- lib/lp/registry/javascript/tests/test_structural_subscription.js 2012-07-31 06:14:21 +0000
358+++ lib/lp/registry/javascript/tests/test_structural_subscription.js 2012-08-07 07:08:19 +0000
359@@ -86,6 +86,9 @@
360 'Invalid', 'Won\'t Fix', 'Expired',
361 'Confirmed', 'Triaged', 'In Progress',
362 'Fix Committed', 'Fix Released', 'Unknown'];
363+ LP.cache.information_types = ['Public', 'Public Security',
364+ 'Private Security', 'Private',
365+ 'Proprietary'];
366 LP.links.me = 'https://launchpad.dev/api/~someone';
367 return original_lp;
368 }
369@@ -124,6 +127,7 @@
370 LP.cache.administratedTeams = [];
371 LP.cache.importances = [];
372 LP.cache.statuses = [];
373+ LP.cache.information_types = [];
374
375 this.configuration = {
376 content_box: content_box_id
377@@ -226,6 +230,7 @@
378 LP.cache.administratedTeams = [];
379 LP.cache.importances = [];
380 LP.cache.statuses = [];
381+ LP.cache.information_types = [];
382
383 this.configuration = {
384 content_box: content_box_id
385@@ -357,6 +362,7 @@
386 LP.cache.administratedTeams = [];
387 LP.cache.importances = [];
388 LP.cache.statuses = [];
389+ LP.cache.information_types = [];
390 LP.links.me = 'https://launchpad.dev/api/~someone';
391
392 var lp_client = function() {};
393@@ -417,6 +423,7 @@
394 LP.cache.administratedTeams = [];
395 LP.cache.importances = [];
396 LP.cache.statuses = [];
397+ LP.cache.information_types = [];
398 LP.links.me = 'https://launchpad.dev/api/~someone';
399
400 var lp_client = function() {};
401@@ -702,6 +709,7 @@
402 description: 'DESCRIPTION',
403 statuses: [],
404 importances: [],
405+ information_types: [],
406 tags: [],
407 find_all_tags: true,
408 bug_notification_level: 'Discussion',
409@@ -737,6 +745,7 @@
410 description: 'DESCRIPTION',
411 statuses: [],
412 importances: [],
413+ information_types: [],
414 tags: [],
415 find_all_tags: true,
416 bug_notification_level: 'Discussion',
417@@ -775,6 +784,7 @@
418 description: 'DESCRIPTION',
419 statuses: [],
420 importances: [],
421+ information_types: [],
422 tags: [],
423 find_all_tags: true,
424 bug_notification_level: 'Discussion',
425@@ -815,6 +825,7 @@
426 description: 'DESCRIPTION',
427 statuses: [],
428 importances: [],
429+ information_types: [],
430 tags: [],
431 find_all_tags: true,
432 bug_notification_level: 'Discussion',
433@@ -1015,7 +1026,8 @@
434 tags: [' one two THREE '],
435 tag_match: [''],
436 importances: [],
437- statuses: []
438+ statuses: [],
439+ information_types: []
440 };
441 var patch_data = module._extract_form_data(form_data);
442 // Note that the tags are converted to lower case
443@@ -1032,7 +1044,8 @@
444 tags: ['tag'],
445 tag_match: ['match-all'],
446 importances: [],
447- statuses: []
448+ statuses: [],
449+ information_types: []
450 };
451 var patch_data = module._extract_form_data(form_data);
452 Assert.isTrue(patch_data.find_all_tags);
453@@ -1046,7 +1059,8 @@
454 tags: ['tag'],
455 tag_match: [],
456 importances: [],
457- statuses: []
458+ statuses: [],
459+ information_types: []
460 };
461 var patch_data = module._extract_form_data(form_data);
462 Assert.isFalse(patch_data.find_all_tags);
463@@ -1063,7 +1077,8 @@
464 tags: ['tag'],
465 tag_match: ['match-all'],
466 importances: ['importance1'],
467- statuses: ['status1']
468+ statuses: ['status1'],
469+ information_types: ['informationtype1']
470 };
471 var patch_data = module._extract_form_data(form_data);
472 // Since advanced-filter isn't set, all the advanced values should
473@@ -1072,6 +1087,7 @@
474 ArrayAssert.isEmpty(patch_data.tags);
475 ArrayAssert.isEmpty(patch_data.importances);
476 ArrayAssert.isEmpty(patch_data.statuses);
477+ ArrayAssert.isEmpty(patch_data.information_types);
478 }
479
480 }));
481@@ -1092,6 +1108,7 @@
482 description: 'DESCRIPTION',
483 statuses: [],
484 importances: [],
485+ information_types: [],
486 tags: [],
487 find_all_tags: true,
488 bug_notification_level: 'Discussion'
489@@ -1183,6 +1200,7 @@
490 description: 'DESCRIPTION',
491 statuses: [],
492 importances: [],
493+ information_types: [],
494 tags: [],
495 find_all_tags: true,
496 bug_notification_level: 'Discussion'
497@@ -1251,6 +1269,7 @@
498 description: 'DESCRIPTION',
499 statuses: [],
500 importances: [],
501+ information_types: [],
502 tags: [],
503 find_all_tags: true,
504 bug_notification_level: 'Discussion',
505@@ -1353,6 +1372,7 @@
506 description: 'DESCRIPTION',
507 statuses: [],
508 importances: [],
509+ information_types: [],
510 tags: [],
511 find_all_tags: true,
512 bug_notification_level: 'Discussion',
513@@ -1513,6 +1533,7 @@
514 return {
515 importances: [],
516 statuses: [],
517+ information_types: [],
518 tags: [],
519 find_all_tags: true,
520 bug_notification_level: 'Discussion',
521@@ -1561,6 +1582,7 @@
522 description: 'DESCRIPTION',
523 statuses: [],
524 importances: [],
525+ information_types: [],
526 tags: [],
527 find_all_tags: true,
528 bug_notification_level: 'Discussion',
529@@ -1580,6 +1602,7 @@
530 return {
531 importances: [],
532 statuses: [],
533+ information_types: [],
534 tags: [],
535 find_all_tags: true,
536 bug_notification_level: 'Discussion',
537@@ -1621,6 +1644,7 @@
538 {filter: {
539 statuses: [],
540 importances: [],
541+ information_types: [],
542 tags: [],
543 find_all_tags: true,
544 bug_notification_level: 'Discussion',
545@@ -1770,6 +1794,7 @@
546 description: 'DESCRIPTION',
547 statuses: [],
548 importances: [],
549+ information_types: [],
550 tags: [],
551 find_all_tags: true,
552 bug_notification_level: 'Discussion',