Merge lp:~danilo/launchpad/add-subscription-link into lp:launchpad

Proposed by Данило Шеган
Status: Merged
Approved by: Данило Шеган
Approved revision: no longer in the source branch.
Merged at revision: 12763
Proposed branch: lp:~danilo/launchpad/add-subscription-link
Merge into: lp:launchpad
Diff against target: 786 lines (+376/-161)
5 files modified
lib/lp/bugs/browser/structuralsubscription.py (+21/-3)
lib/lp/bugs/browser/tests/test_expose.py (+3/-3)
lib/lp/bugs/templates/bugtarget-subscription-list.pt (+12/-2)
lib/lp/registry/javascript/structural-subscription.js (+334/-151)
lib/lp/registry/javascript/tests/test_structural_subscription.js (+6/-2)
To merge this branch: bzr merge lp:~danilo/launchpad/add-subscription-link
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+56392@code.launchpad.net

Commit message

[r=gmb][bug=752410] Provide 'add subscription' link on target:+subscriptions page along with some refactoring of structural-subscriptions.js.

Description of the change

= Provide 'add subscription' from target:+subscriptions =

Allow creating a target subscription from the subscriptions overview page.
Lots of refactoring of the existing code (mostly moving stuff around and generalizing so we can get all the data we need and render the stuff with the same code).

== Tests ==

No tests atm. Will do them as a separate branch.

== Demo and Q/A ==

https://devpad.canonical.com/~danilo/screencasts/add-subscriptions.ogv

https://launchpad.dev/firefox/+subscriptions

https://launchpad.dev/firefox/ ("Subscribe to bug mail" link keeps working)

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/browser/structuralsubscription.py
  lib/lp/bugs/templates/bugtarget-subscription-list.pt
  lib/lp/registry/javascript/structural-subscription.js

./lib/lp/registry/javascript/structural-subscription.js
     209: Line exceeds 78 characters.
     672: Line exceeds 78 characters.
     710: Line exceeds 78 characters.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

Just one comment; otherwise this is good to land.

357 + .set('id', 'subscription-filter-'+filter_id.toString());
358 + filter_node.appendChild(Y.Node.create(
359 + '<div style="margin-top: 1em"></div>'));
360 + filter_node.appendChild(Y.Node.create(
361 + '<strong class="filter-name"></strong>'));
362 +
363 + if (!filter_info.subscriber_is_team ||
364 + filter_info.user_is_team_admin) {
365 + // User can edit the subscription.
366 + filter_node.appendChild(Y.Node.create(
367 + '<span style="float: right">'+
368 + '<a href="#" class="sprite modify edit js-action '+
369 + ' edit-subscription">'+
370 + ' Edit this subscription</a> or '+
371 + '<a href="#" class="sprite modify remove js-action '+
372 + ' delete-subscription">'+
373 + ' Unsubscribe</a></span>'));
374 + } else {
375 + // User cannot edit the subscription, because this is a
376 + // team and the user does not have admin privileges.
377 + filter_node.appendChild(Y.Node.create(
378 + '<span style="float: right"><em>'+
379 + 'You do not have privileges to change this subscription'+
380 + '</em></span>'));
381 + }
382 +
383 + var description_node = filter_node.appendChild(
384 + Y.Node.create(
385 + '<div style="padding-left: 1em"></div>')
386 + .set('id', 'filter-description-'+filter_id.toString()));
387 +

Unless there's a reason for setting the style manually, you should create a class for this and add it to the core CSS.

review: Approve (code)
Revision history for this message
Данило Шеган (danilo) wrote :

As explained on IRC, this is just moved code. structural-subscriptions.js has more of style manipulations, I filed bug 752406 about it.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/structuralsubscription.py'
2--- lib/lp/bugs/browser/structuralsubscription.py 2011-04-04 18:51:17 +0000
3+++ lib/lp/bugs/browser/structuralsubscription.py 2011-04-06 16:01:18 +0000
4@@ -33,6 +33,7 @@
5 from zope.traversing.browser import absoluteURL
6
7 from canonical.launchpad.webapp.authorization import check_permission
8+from canonical.launchpad.webapp.interfaces import NoCanonicalUrl
9 from canonical.launchpad.webapp.menu import (
10 enabled_with_permission,
11 Link,
12@@ -390,10 +391,13 @@
13 expose_user_administered_teams_to_js(request, user, context)
14 expose_enum_to_js(request, BugTaskImportance, 'importances')
15 expose_enum_to_js(request, BugTaskStatus, 'statuses')
16- if subscriptions is None:
17+ if subscriptions is None or len(list(subscriptions)) == 0:
18 subscriptions = []
19+ target = context
20+ else:
21+ target = None
22 expose_user_subscriptions_to_js(
23- user, subscriptions, request)
24+ user, subscriptions, request, target)
25
26
27 def expose_enum_to_js(request, enum, name):
28@@ -421,11 +425,12 @@
29 info.append({
30 'link': absoluteURL(team, api_request),
31 'title': team.title,
32+ 'url': canonical_url(team),
33 })
34 IJSONRequestCache(request).objects['administratedTeams'] = info
35
36
37-def expose_user_subscriptions_to_js(user, subscriptions, request):
38+def expose_user_subscriptions_to_js(user, subscriptions, request, target=None):
39 """Make the user's subscriptions available to JavaScript."""
40 info = {}
41 api_request = IWebServiceClientRequest(request)
42@@ -433,6 +438,19 @@
43 administered_teams = []
44 else:
45 administered_teams = user.getAdministratedTeams()
46+
47+ if target is not None:
48+ try:
49+ # No subscriptions, which means we are on a target
50+ # subscriptions page. Let's at least provide target details.
51+ target_info = {}
52+ target_info['title'] = target.title
53+ target_info['url'] = canonical_url(target, rootsite='mainsite')
54+ IJSONRequestCache(request).objects['target_info'] = target_info
55+ except NoCanonicalUrl:
56+ # We export nothing if the target implements no canonical URL.
57+ pass
58+
59 for subscription in subscriptions:
60 target = subscription.target
61 record = info.get(target)
62
63=== modified file 'lib/lp/bugs/browser/tests/test_expose.py'
64--- lib/lp/bugs/browser/tests/test_expose.py 2011-03-31 15:04:53 +0000
65+++ lib/lp/bugs/browser/tests/test_expose.py 2011-04-06 16:01:18 +0000
66@@ -132,7 +132,7 @@
67 self.assertThat(len(team_info), Equals(expected_number_teams))
68 # The items info consist of a dictionary with link and title keys.
69 for i in range(expected_number_teams):
70- self.assertThat(team_info[i], KeysEqual('link', 'title'))
71+ self.assertThat(team_info[i], KeysEqual('link', 'title', 'url'))
72 # The link is the title of the team.
73 self.assertThat(
74 team_info[0]['title'], Equals(u'Bug Supervisor Sub Team'))
75@@ -165,7 +165,7 @@
76 self.assertThat(len(team_info), Equals(expected_number_teams))
77 # The items info consist of a dictionary with link and title keys.
78 for i in range(expected_number_teams):
79- self.assertThat(team_info[i], KeysEqual('link', 'title'))
80+ self.assertThat(team_info[i], KeysEqual('link', 'title', 'url'))
81 # The link is the title of the team.
82 self.assertThat(
83 team_info[0]['title'], Equals(u'Bug Supervisor Sub Team'))
84@@ -193,7 +193,7 @@
85 self.assertThat(len(team_info), Equals(expected_number_teams))
86 # The items info consist of a dictionary with link and title keys.
87 for i in range(expected_number_teams):
88- self.assertThat(team_info[i], KeysEqual('link', 'title'))
89+ self.assertThat(team_info[i], KeysEqual('link', 'title', 'url'))
90 # The link is the title of the team.
91 self.assertThat(
92 team_info[0]['title'], Equals(u'Bug Supervisor Sub Team'))
93
94=== modified file 'lib/lp/bugs/templates/bugtarget-subscription-list.pt'
95--- lib/lp/bugs/templates/bugtarget-subscription-list.pt 2011-03-29 22:34:04 +0000
96+++ lib/lp/bugs/templates/bugtarget-subscription-list.pt 2011-04-06 16:01:18 +0000
97@@ -17,9 +17,13 @@
98 request/features/malone.advanced-structural-subscriptions.enabled">
99 LPS.use('lp.registry.structural_subscription', function(Y) {
100 var module = Y.lp.registry.structural_subscription;
101+ var config = {
102+ content_box: "#structural-subscription-content-box",
103+ add_filter_description: true};
104 Y.on('domready', function() {
105- module.setup_bug_subscriptions(
106- {content_box: "#structural-subscription-content-box"})
107+ module.setup_bug_subscriptions(config);
108+ module.setup_subscription_link(
109+ config, '#create-new-subscription');
110 });
111 });
112 </script>
113@@ -31,6 +35,12 @@
114
115 <div id="maincontent">
116 <div id="nonportlets" class="readable">
117+ <div tal:condition="
118+ features/malone.advanced-structural-subscriptions.enabled">
119+ <a class="sprite add" id="create-new-subscription"
120+ href="#">Add a subscription</a>
121+ </div>
122+
123 <div id="subscription-listing"></div>
124
125 <div id="structural-subscription-content-box"></div>
126
127=== modified file 'lib/lp/registry/javascript/structural-subscription.js'
128--- lib/lp/registry/javascript/structural-subscription.js 2011-04-04 16:22:20 +0000
129+++ lib/lp/registry/javascript/structural-subscription.js 2011-04-06 16:01:18 +0000
130@@ -164,20 +164,27 @@
131 * @method create_structural_subscription filter
132 * @param {Object} who Link to the user or team to be subscribed.
133 * @param {Object} form_data The data returned from the form submission.
134+ * @param {Object} success_callback Function to execute when filter is added.
135 */
136-function add_bug_filter(who, form_data) {
137+function add_bug_filter(who, form_data, success_callback) {
138 var config = {
139 on: {success: function (bug_filter) {
140 // If we fail to PATCH the new bug filter, DELETE it.
141- var on = {failure: function () {
142- // We use the namespace binding so tests can override
143- // these functions.
144- namespace._delete_filter(bug_filter);
145- // Call the failure handler to report the original
146- // error to the user.
147- overlay_error_handler.getFailureHandler()
148- .apply(this, arguments);
149- }};
150+ var on = {
151+ failure: function () {
152+ // We use the namespace binding so tests can override
153+ // these functions.
154+ namespace._delete_filter(bug_filter);
155+ // Call the failure handler to report the original
156+ // error to the user.
157+ overlay_error_handler.getFailureHandler()
158+ .apply(this, arguments);
159+ },
160+ success: function (bug_filter) {
161+ success_callback(form_data, bug_filter);
162+ subscription_success(bug_filter);
163+ }
164+ };
165 patch_bug_filter(bug_filter.getAttrs(), form_data, on);
166 },
167 failure: overlay_error_handler.getFailureHandler()
168@@ -195,28 +202,31 @@
169 namespace._add_bug_filter = add_bug_filter;
170
171 /**
172- * Given the form data from a user, save the subscription.
173+ * Create a handler to save the subscription given the form data from a user.
174 *
175 * @private
176- * @method save_subscription
177- * @param {Object} form_data The data generated by the form submission.
178+ * @method make_add_subscription_handler
179+ * @param {Object} success_callback Function to execute on successful addition.
180 */
181-
182-function save_subscription(form_data) {
183- var who;
184- var has_errors = check_for_errors_in_overlay(add_subscription_overlay);
185- if (has_errors) {
186- return false;
187- }
188- if (form_data.recipient[0] === 'user') {
189- who = LP.links.me;
190- } else {
191- // There can be only one.
192- who = form_data.team[0];
193- }
194- add_bug_filter(who, form_data);
195+function make_add_subscription_handler(success_callback) {
196+ var save_subscription = function(form_data) {
197+ var who;
198+ var has_errors = check_for_errors_in_overlay(
199+ add_subscription_overlay);
200+ if (has_errors) {
201+ return false;
202+ }
203+ if (form_data.recipient[0] === 'user') {
204+ who = LP.links.me;
205+ } else {
206+ // There can be only one.
207+ who = form_data.team[0];
208+ }
209+ return add_bug_filter(who, form_data, success_callback);
210+ };
211+ return save_subscription;
212 }
213-namespace.save_subscription = save_subscription;
214+namespace._make_add_subscription_handler = make_add_subscription_handler;
215
216 function check_for_errors_in_overlay(overlay) {
217 var has_errors = false;
218@@ -245,6 +255,18 @@
219 }
220
221 /**
222+ * Fill the filter name and description.
223+ */
224+function fill_filter_description(description_node, filter_info, filter) {
225+ description_node
226+ .empty()
227+ .appendChild(create_filter_description(filter));
228+ description_node.ancestor('.subscription-filter').one('.filter-name')
229+ .empty()
230+ .appendChild(render_filter_title(filter_info, filter));
231+};
232+
233+/**
234 * Handle the activation of the edit subscription link.
235 */
236 function edit_subscription_handler(context, form_data) {
237@@ -254,14 +276,10 @@
238 return false;
239 }
240 var on = {success: function (new_data) {
241+ var description_node = Y.one(filter_id);
242 var filter = new_data.getAttrs();
243- var description_node = Y.one(filter_id);
244- description_node
245- .empty()
246- .appendChild(create_filter_description(filter));
247- description_node.ancestor('.subscription-filter').one('.filter-name')
248- .empty()
249- .appendChild(render_filter_title(context.filter_info, filter));
250+ fill_filter_description(
251+ description_node, context.filter_info, filter);
252 add_subscription_overlay.hide();
253 }};
254 patch_bug_filter(context.filter_info.filter, form_data, on);
255@@ -279,7 +297,7 @@
256 * Populate the overlay element with the contents of the add/edit form.
257 */
258 function create_overlay(content_box_id, overlay_id, submit_button,
259- submit_callback) {
260+ submit_callback, success_callback) {
261 // Create the overlay.
262 add_subscription_overlay = new Y.lazr.FormOverlay({
263 headerContent:
264@@ -540,14 +558,18 @@
265 });
266 });
267 anim.on("end", function() {
268- node.setStyles({
269- height: 0,
270- visibility: 'hidden',
271- display: null
272- // Don't set display: none because then the node won't be taken
273- // into account and the rendering will sometimes jiggle
274- // horizontally when the node is opened.
275- });
276+ if (user_cfg && user_cfg.remove_on_end === true) {
277+ node.remove();
278+ } else {
279+ node.setStyles({
280+ height: 0,
281+ visibility: 'hidden',
282+ display: null
283+ // Don't set display: none because then the node won't be taken
284+ // into account and the rendering will sometimes jiggle
285+ // horizontally when the node is opened.
286+ });
287+ }
288 });
289 anim.run();
290 }
291@@ -881,13 +903,8 @@
292 var recipient_label = content_node.one('input[name="recipient"] + span'),
293 teams = LP.cache.administratedTeams;
294 if (filter_info !== undefined && filter_info.subscriber_is_team) {
295- var i;
296- for (i=0; i<teams.length; i++) {
297- if (teams[i].link === filter_info.subscriber_link){
298- recipient_label.set('text', teams[i].title);
299- break;
300- }
301- }
302+ var team = get_team(filter_info.subscriber_link);
303+ recipient_label.set('text', team.title);
304 } else {
305 recipient_label.set('text', 'Yourself');
306 }
307@@ -1040,25 +1057,50 @@
308 var y_config = {
309 method: "POST",
310 headers: {'X-HTTP-Method-Override': 'DELETE'},
311- on: {success: function(transactionid, response, args){
312+ on: {
313+ success: function(transactionid, response, args){
314+ var filter_node = Y.one(
315+ '#subscription-filter-'+filter_id.toString());
316+ filter_node.setStyle("margin-top", "0");
317 var subscriber = Y.one(
318 '#subscription-'+subscriber_id.toString());
319- var to_collapse = subscriber;
320 var filters = subscriber.all('.subscription-filter');
321- if (!filters.isEmpty()) {
322- to_collapse = Y.one(
323- '#subscription-filter-'+filter_id.toString());
324- }
325- collapse_node(to_collapse);
326- },
327- failure: error_handler.getFailureHandler()
328- }
329- };
330+
331+ collapse_node(filter_node, { remove_on_end: true });
332+ if (filters.size() <= 1) {
333+ collapse_node(subscriber, { remove_on_end: true });
334+ LP.cache.subscription_info[subscriber_id].filters = [];
335+ }
336+ },
337+ failure: error_handler.getFailureHandler()
338+ }
339+ };
340 Y.io(filter.self_link, y_config);
341 };
342 }
343
344 /**
345+ * Attach activation (click) handlers to links for a particular filter.
346+ */
347+function wire_up_edit_links_for_filter(
348+ config, subscription, subscription_id, filter_info, filter_id,
349+ filter_node) {
350+ if (!filter_info.subscriber_is_team ||
351+ filter_info.user_is_team_admin) {
352+ var node = filter_node || Y.one(
353+ '#subscription-filter-'+filter_id.toString());
354+ var edit_link = node.one('a.edit-subscription');
355+ var edit_handler = make_edit_handler(
356+ subscription, filter_info, filter_id, config);
357+ edit_link.on('click', edit_handler);
358+ var delete_link = node.one('a.delete-subscription');
359+ var delete_handler = make_delete_handler(
360+ filter_info.filter, filter_id, subscription_id);
361+ delete_link.on('click', delete_handler);
362+ }
363+}
364+
365+/**
366 * Attach activation (click) handlers to all of the edit links on the page.
367 */
368 function wire_up_edit_links(config) {
369@@ -1071,25 +1113,96 @@
370 var sub = subscription_info[i];
371 for (j=0; j<sub.filters.length; j++) {
372 var filter_info = sub.filters[j];
373- if (!filter_info.subscriber_is_team ||
374- filter_info.user_is_team_admin) {
375- var node = Y.one(
376- '#subscription-filter-'+filter_id.toString());
377- var edit_link = node.one('a.edit-subscription');
378- var edit_handler = make_edit_handler(
379- sub, filter_info, filter_id, config);
380- edit_link.on('click', edit_handler);
381- var delete_link = node.one('a.delete-subscription');
382- var delete_handler = make_delete_handler(
383- filter_info.filter, filter_id, i);
384- delete_link.on('click', delete_handler);
385- }
386+ wire_up_edit_links_for_filter(
387+ config, sub, i, filter_info, filter_id);
388 filter_id += 1;
389 }
390 }
391 }
392
393 /**
394+ * Create filter node to include in the subscription's filter listing.
395+ */
396+function create_filter_node(filter_id, filter_info, filter) {
397+ var filter_node = Y.Node.create(
398+ '<div style="margin: 1em 0em 0em 1em"'+
399+ ' class="subscription-filter"></div>')
400+ .set('id', 'subscription-filter-'+filter_id.toString());
401+ filter_node.appendChild(Y.Node.create(
402+ '<div style="margin-top: 1em"></div>'));
403+ filter_node.appendChild(Y.Node.create(
404+ '<strong class="filter-name"></strong>'));
405+
406+ if (!filter_info.subscriber_is_team ||
407+ filter_info.user_is_team_admin) {
408+ // User can edit the subscription.
409+ filter_node.appendChild(Y.Node.create(
410+ '<span style="float: right">'+
411+ '<a href="#" class="sprite modify edit js-action '+
412+ ' edit-subscription">'+
413+ ' Edit this subscription</a> or '+
414+ '<a href="#" class="sprite modify remove js-action '+
415+ ' delete-subscription">'+
416+ ' Unsubscribe</a></span>'));
417+ } else {
418+ // User cannot edit the subscription, because this is a
419+ // team and the user does not have admin privileges.
420+ filter_node.appendChild(Y.Node.create(
421+ '<span style="float: right"><em>'+
422+ 'You do not have privileges to change this subscription'+
423+ '</em></span>'));
424+ }
425+
426+ var description_node = filter_node.appendChild(
427+ Y.Node.create(
428+ '<div style="padding-left: 1em"></div>')
429+ .set('id', 'filter-description-'+filter_id.toString()));
430+
431+ fill_filter_description(description_node, filter_info, filter);
432+
433+ return filter_node;
434+}
435+
436+/**
437+ * Create a node with subscription description.
438+ */
439+function create_subscription_node(serial_id, subscription_data, filter_id) {
440+ var node = Y.Node.create(
441+ '<div style="margin-top: 2em; padding: 0 1em 1em 1em; '+
442+ ' border: 1px solid #ddd;"></div>')
443+ .set('id', 'subscription-'+serial_id.toString())
444+ node.appendChild(Y.Node.create(
445+ ' <span style="float: left; margin-top: -0.6em; '+
446+ ' padding: 0 1ex; background-color: #fff;"></a>'))
447+ .appendChild('<span>Subscriptions to </span>')
448+ .appendChild(Y.Node.create('<a></a>')
449+ .set('href', subscription_data.target_url)
450+ .set('text', subscription_data.target_title));
451+
452+ // We can remove this once we enforce at least one filter per
453+ // subscription.
454+ if (subscription_data.filters.length === 0) {
455+ node.appendChild(
456+ '<div style="clear: both; padding: 1em 0 0 1em"></div>')
457+ .appendChild('<strong>All messages</strong>');
458+ }
459+
460+ for (j=0; j<subscription_data.filters.length; j++) {
461+ var filter_info = subscription_data.filters[j];
462+ var filter = filter_info.filter;
463+ // We put the filters in the cache so that the patch mechanism
464+ // can automatically find them and update them on a successful
465+ // edit. This makes it possible to open up a filter after an edit
466+ // and see the information you expect to see.
467+ LP.cache['structural-subscription-filter-'+filter_id.toString()] =
468+ filter;
469+ node.appendChild(create_filter_node(filter_id, filter_info, filter));
470+ filter_id += 1;
471+ };
472+ return node;
473+}
474+
475+/**
476 * Populate the subscription list DOM element with subscription descriptions.
477 */
478 function fill_in_bug_subscriptions(config) {
479@@ -1099,77 +1212,14 @@
480 var subscription_info = LP.cache.subscription_info;
481 var top_node = Y.Node.create(
482 '<div class="yui-g"><div id="structural-subscriptions"></div></div>');
483+ var i;
484 var filter_id = 0;
485- var i;
486- var j;
487 for (i=0; i<subscription_info.length; i++) {
488- var sub = subscription_info[i];
489- var sub_node = top_node.appendChild(Y.Node.create(
490- '<div style="margin-top: 2em; padding: 0 1em 1em 1em; '+
491- ' border: 1px solid #ddd;"></div>')
492- .set('id', 'subscription-'+i.toString()));
493- sub_node.appendChild(Y.Node.create(
494- ' <span style="float: left; margin-top: -0.6em; '+
495- ' padding: 0 1ex; background-color: #fff;"></a>'))
496- .appendChild('<span>Subscriptions to </span>')
497- .appendChild(Y.Node.create('<a></a>')
498- .set('href', sub.target_url)
499- .set('text', sub.target_title));
500-
501- for (j=0; j<sub.filters.length; j++) {
502- var filter = sub.filters[j].filter;
503- // We put the filters in the cache so that the patch mechanism
504- // can automatically find them and update them on a successful
505- // edit. This makes it possible to open up a filter after an edit
506- // and see the information you expect to see.
507- LP.cache['structural-subscription-filter-'+filter_id.toString()] =
508- filter;
509- var filter_node = sub_node.appendChild(Y.Node.create(
510- '<div style="margin: 1em 0em 0em 1em"'+
511- ' class="subscription-filter"></div>')
512- .set('id', 'subscription-filter-'+filter_id.toString()))
513- .appendChild(Y.Node.create(
514- '<div style="margin-top: 1em"></div>'));
515- filter_node.appendChild(Y.Node.create(
516- '<strong class="filter-name"></strong>'))
517- .appendChild(render_filter_title(sub.filters[j], filter));
518-
519- if (!sub.filters[j].subscriber_is_team ||
520- sub.filters[j].user_is_team_admin) {
521- // User can edit the subscription.
522- filter_node.appendChild(Y.Node.create(
523- '<span style="float: right">'+
524- '<a href="#" class="sprite modify edit js-action '+
525- ' edit-subscription">'+
526- ' Edit this subscription</a> or '+
527- '<a href="#" class="sprite modify remove js-action '+
528- ' delete-subscription">'+
529- ' Unsubscribe</a></span>'));
530- } else {
531- // User cannot edit the subscription, because this is a
532- // team and the user does not have admin privileges.
533- filter_node.appendChild(Y.Node.create(
534- '<span style="float: right"><em>'+
535- 'You do not have privileges to change this subscription'+
536- '</em></span>'));
537- }
538-
539- filter_node.appendChild(Y.Node.create(
540- '<div style="padding-left: 1em"></div>')
541- .set('id', 'filter-description-'+filter_id.toString()))
542- .appendChild(create_filter_description(filter));
543-
544- filter_id += 1;
545- }
546-
547- // We can remove this once we enforce at least one filter per
548- // subscription.
549- if (subscription_info[i].filters.length === 0) {
550- sub_node.appendChild(
551- '<div style="clear: both; padding: 1em 0 0 1em"></div>')
552- .appendChild('<strong>All messages</strong>');
553- }
554+ top_node.appendChild(
555+ create_subscription_node(i, subscription_info[i], filter_id));
556+ filter_id += subscription_info[i].filters.length;
557 }
558+
559 listing.appendChild(top_node);
560
561 wire_up_edit_links(config);
562@@ -1203,7 +1253,6 @@
563 */
564 function create_filter_description(filter) {
565 var description = Y.Node.create('<div></div>');
566-
567 var filter_items = [];
568 // Format status conditions.
569 if (filter.statuses.length !== 0) {
570@@ -1303,20 +1352,153 @@
571 }
572
573 /**
574+ * Get team information.
575+ */
576+function get_team(url) {
577+ var teams = LP.cache.administratedTeams;
578+ var i;
579+ for (i=0; i<teams.length; i++) {
580+ if (teams[i].link === url) {
581+ return teams[i];
582+ }
583+ }
584+}
585+
586+/**
587+ * Get team information from the submitted form data.
588+ */
589+function get_team_info(form_data) {
590+ var is_team = (form_data.recipient[0] == "team");
591+ var link, team, title, url;
592+ if (is_team) {
593+ link = form_data.team[0];
594+ team = get_team(link);
595+ if (team !== undefined) {
596+ title = team.title;
597+ url = team.url;
598+ } else {
599+ is_team = false;
600+ link = LP.links.me;
601+ }
602+ }
603+ return {
604+ is_team: is_team,
605+ link: link,
606+ title: title,
607+ url: url}
608+}
609+
610+/**
611+ * Get target details from either target_info or first subscription_info.
612+ */
613+function get_target_info() {
614+ if (LP.cache.target_info !== undefined) {
615+ return LP.cache.target_info
616+ } else {
617+ var info = LP.cache.subscription_info[0];
618+ return {
619+ title: info.target_title,
620+ url: info.target_url}
621+ }
622+}
623+
624+/**
625+ * Constructs filter info on-the-go.
626+ */
627+function construct_filter_info(filter, form_data, target_info) {
628+ var team_info = get_team_info(form_data);
629+
630+ var filter_info = {
631+ filter : filter,
632+ subscriber_is_team: team_info.is_team,
633+ user_is_team_admin: team_info.is_team,
634+ subscriber_url: team_info.url,
635+ subscriber_link: team_info.link,
636+ subscriber_title: team_info.title,
637+ target_title: target_info.title,
638+ target_url: target_info.url
639+ };
640+ return filter_info;
641+}
642+
643+/**
644+ * Return a function that adds the newly created filter to the
645+ * subscription's filter list after it has been saved.
646+ */
647+
648+function make_add_subscription_success_handler(config) {
649+ return function(form_data, filter) {
650+ if (config.add_filter_description === true) {
651+ // This way to figure out the ID works only
652+ // if the page shows exactly one "target" (eg. Firefox).
653+ var subscriber_id = 0;
654+ var filter_id;
655+ var subscriptions_list = Y.one(
656+ '#subscription-' + subscriber_id.toString());
657+
658+ var filter_data = filter.getAttrs();
659+ var target_info = get_target_info();
660+ var filter_info = construct_filter_info(
661+ filter_data, form_data, target_info);
662+
663+ /* Node to flash with green on success. */
664+ var anim_node;
665+
666+ var subscription_info;
667+ if (subscriptions_list === null) {
668+ // No subscriptions are listed at all.
669+ filter_id = 0;
670+ subscription_info = {
671+ filters: [filter_info],
672+ target_url: target_info.url,
673+ target_title: target_info.title
674+ };
675+ LP.cache.subscription_info = [subscription_info]
676+
677+ subscriptions_list = create_subscription_node(
678+ 0, subscription_info, 0);
679+ var subscriptions = Y.one("#structural-subscriptions");
680+ subscriptions.appendChild(subscriptions_list);
681+ anim_node = subscriptions_list.one("#subscription-filter-0");
682+ } else {
683+ // There's at least one filter in the page.
684+ subscription_info = LP.cache.subscription_info[0];
685+ filter_id = subscription_info.filters.length;
686+ subscription_info.filters.push(filter);
687+
688+ var description_node = create_filter_node(
689+ filter_id, filter_info, filter_data);
690+ subscriptions_list.append(description_node);
691+ anim_node = description_node;
692+ }
693+
694+ wire_up_edit_links_for_filter(
695+ config, subscription_info, 0,
696+ filter_info, filter_id, anim_node);
697+ Y.lazr.anim.green_flash({node: anim_node}).run();
698+ }
699+ }
700+}
701+
702+/**
703 * Show the overlay for creating a new subscription.
704 */
705 function show_add_overlay(config) {
706- Y.one(config.content_box).empty();
707+ var content_node = Y.one(config.content_box);
708+ content_node.empty();
709 var overlay_id = setup_overlay(config.content_box);
710- clear_overlay(Y.one(config.content_box), false);
711+ clear_overlay(content_node, false);
712
713 var submit_button = Y.Node.create(
714 '<button type="submit" name="field.actions.create" ' +
715 'value="Create subscription" class="lazr-pos lazr-btn" '+
716 '>OK</button>');
717
718+ var success_callback = make_add_subscription_success_handler(config);
719+
720+ var save_subscription = make_add_subscription_handler(success_callback);
721 create_overlay(config.content_box, overlay_id, submit_button,
722- save_subscription);
723+ save_subscription, success_callback);
724 // We need to initialize the help links. They may have already been
725 // initialized except for the ones we added, so setupHelpTrigger
726 // is idempotent. Notice that this is old MochiKit code.
727@@ -1337,7 +1519,7 @@
728 // Modify the menu-link-subscribe-to-bug-mail link to be visible.
729 var link = Y.one(link_id);
730 if (!Y.Lang.isValue(link)) {
731- Y.fail('Link to set as the pop-up link not found.');
732+ Y.Assert.fail('Link to set as the pop-up link not found.');
733 }
734 link.removeClass('invisible-link');
735 link.addClass('visible-link');
736@@ -1347,6 +1529,7 @@
737 });
738 link.addClass('js-action');
739 } // setup_subscription_links
740+namespace.setup_subscription_link = setup_subscription_link;
741
742 /**
743 * External entry point for configuring the structural subscription.
744@@ -1366,6 +1549,6 @@
745 }; // setup
746
747 }, '0.1', {requires: [
748- 'dom', 'node', 'lazr.anim', 'lazr.formoverlay', 'lazr.overlay',
749- 'lazr.effects', 'lp.app.errors', 'lp.client', 'gallery-accordion'
750- ]});
751+ 'dom', 'node', 'test', 'lazr.anim', 'lazr.formoverlay', 'lazr.overlay',
752+ 'lazr.effects', 'lp.app.errors', 'lp.client', 'gallery-accordion'
753+]});
754
755=== modified file 'lib/lp/registry/javascript/tests/test_structural_subscription.js'
756--- lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-04-04 15:59:28 +0000
757+++ lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-04-06 16:01:18 +0000
758@@ -202,6 +202,10 @@
759 recipient: ['user']
760 };
761 context = {};
762+
763+ // Get the save subscription handler with empty success handler.
764+ this.save_subscription = module._make_add_subscription_handler(
765+ function() {});
766 },
767
768 tearDown: function() {
769@@ -216,7 +220,7 @@
770 module.setup(this.configuration);
771 module._show_add_overlay(this.configuration);
772 this.form_data.recipient = ['user'];
773- module.save_subscription(this.form_data);
774+ this.save_subscription(this.form_data);
775 Assert.areEqual(
776 LP.links.me,
777 context.config.parameters.subscriber);
778@@ -229,7 +233,7 @@
779 module._show_add_overlay(this.configuration);
780 this.form_data.recipient = ['team'];
781 this.form_data.team = ['https://launchpad.dev/api/~super-team'];
782- module.save_subscription(this.form_data);
783+ this.save_subscription(this.form_data);
784 Assert.areEqual(
785 this.form_data.team[0],
786 context.config.parameters.subscriber);