~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/javascript/subscription.js

  • Committer: Ian Booth
  • Date: 2011-04-19 15:10:57 UTC
  • mfrom: (12868 devel)
  • mto: This revision was merged to the branch mainline in revision 12983.
  • Revision ID: ian.booth@canonical.com-20110419151057-he56y6k29c4zeiyk
MergeĀ fromĀ trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
95
95
    ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATE: (
96
96
        _ADMIN_BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATE),
97
97
    ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATES: (
98
 
        _ADMIN_BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATES),
 
98
        _ADMIN_BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATES)
99
99
});
100
100
 
101
101
/* These are the owner variations. */
102
102
var _OWNER = (
103
 
    "the owner of the {pillar_type} " +
104
 
        "{pillar}, which has no bug supervisor.");
 
103
    "the owner of {pillar}, which has no bug supervisor.");
105
104
/* These are the actual strings to use. */
106
105
Y.mix(reasons, {
107
106
    YOU_OWNER: _BECAUSE_YOU_ARE + _OWNER,
108
107
    TEAM_OWNER: _BECAUSE_TEAM_IS + _OWNER,
109
 
    ADMIN_TEAM_OWNER: _ADMIN_BECAUSE_TEAM_IS + _OWNER,
 
108
    ADMIN_TEAM_OWNER: _ADMIN_BECAUSE_TEAM_IS + _OWNER
110
109
});
111
110
 
 
111
/* These are the actions */
 
112
 
 
113
/**
 
114
 * This takes an array of ObjectLinks and a url_suffix, and returns a new
 
115
 * array of new ObjectLinks based on the input array, but with the suffix
 
116
 * appended to each original ObjectLink's url.
 
117
 */
 
118
function add_url_element_to_links(links, url_suffix) {
 
119
    var result = [];
 
120
    var index;
 
121
    for (index = 0; index < links.length; index++) {
 
122
        var original = links[index];
 
123
        result.push(ObjectLink(
 
124
            original.self, original.title, original.url + url_suffix));
 
125
    }
 
126
    return result;
 
127
}
 
128
namespace._add_url_element_to_links = add_url_element_to_links;
 
129
 
 
130
function lp_client() {
 
131
    // This is a hook point for tests.
 
132
    if (!Y.Lang.isValue(namespace._lp_client)) {
 
133
        namespace._lp_client = new Y.lp.client.Launchpad();
 
134
    }
 
135
    return namespace._lp_client;
 
136
}
 
137
 
 
138
/**
 
139
 * Helper to find the appropriate link text and get a list of
 
140
 * subscriptions that need to be unsubscribed for duplicate
 
141
 * subscriptions for a person/team.
 
142
 */
 
143
function get_unsubscribe_duplicates_text_and_subscriptions(args) {
 
144
    var text;
 
145
    var subscriptions = [];
 
146
    var index;
 
147
    if (Y.Lang.isValue(args.teams)) {
 
148
        // Unsubscribe team.
 
149
 
 
150
        // There should never be more than one team.
 
151
        if (args.teams.length !== 1) {
 
152
            Y.error('We can only unsubscribe a single team from ' +
 
153
                    'multiple duplicate bugs.');
 
154
        }
 
155
        // Collect all pairs of (team, dupe-bug) that need to be
 
156
        // unsubscribed.
 
157
        for (index = 0; index < args.bugs.length; index++) {
 
158
            subscriptions.push({
 
159
                subscriber: args.teams[0].self.self_link,
 
160
                bug: args.bugs[index].self.self_link
 
161
            });
 
162
        }
 
163
        text = choose_by_number(
 
164
            args.bugs.length,
 
165
            'Unsubscribe this team from the duplicate',
 
166
            'Unsubscribe this team from all duplicates');
 
167
    } else {
 
168
        // Unsubscribe person.
 
169
 
 
170
        // Collect all pairs of (team, dupe-bug) that need to be
 
171
        // unsubscribed.
 
172
        for (index = 0; index < args.bugs.length; index++) {
 
173
            subscriptions.push({
 
174
                subscriber: LP.links.me,
 
175
                bug: args.bugs[index].self.self_link
 
176
            });
 
177
        }
 
178
        text = choose_by_number(
 
179
            args.bugs.length,
 
180
            'Unsubscribe yourself from the duplicate',
 
181
            'Unsubscribe yourself from all duplicates');
 
182
    }
 
183
    return {
 
184
        text: text,
 
185
        subscriptions: subscriptions
 
186
    };
 
187
}
 
188
namespace._get_unsubscribe_duplicates_text_and_subscriptions =
 
189
        get_unsubscribe_duplicates_text_and_subscriptions;
 
190
 
 
191
/**
 
192
 * Helper to find the appropriate link text and get a list of
 
193
 * subscriptions that need to be unsubscribed for team subscriptions.
 
194
 */
 
195
function get_team_unsubscribe_text_and_subscriptions(args) {
 
196
    var subscriptions = [];
 
197
    var index;
 
198
    var text = choose_by_number(args.teams.length,
 
199
                                'Unsubscribe this team',
 
200
                                'Unsubscribe all of these teams');
 
201
    for (index = 0; index < args.teams.length; index++) {
 
202
        subscriptions.push({
 
203
            subscriber: args.teams[index].self.self_link,
 
204
            bug: LP.cache.context.bug_link
 
205
        });
 
206
    }
 
207
    return {
 
208
        text: text,
 
209
        subscriptions: subscriptions
 
210
    };
 
211
}
 
212
namespace._get_team_unsubscribe_text_and_subscriptions =
 
213
        get_team_unsubscribe_text_and_subscriptions;
 
214
 
 
215
/**
 
216
 * Returns a link node with on-click handler that unsubscribes all
 
217
 * subscriptions listed in `subscriptions` and link text set to `text`.
 
218
 */
 
219
function get_node_for_unsubscribing(text, subscriptions) {
 
220
    var node = Y.Node.create(
 
221
        '<a href="#" class="sprite modify remove js-action"></a>');
 
222
    var client = lp_client();
 
223
    var handler = new Y.lp.client.ErrorHandler();
 
224
 
 
225
    node.set('text', text);
 
226
 
 
227
    handler.showError = function(error_msg) {
 
228
        Y.lp.app.errors.display_error(node, error_msg);
 
229
    };
 
230
    handler.clearProgressUI = function () {
 
231
        node.replaceClass('spinner', 'remove');
 
232
    };
 
233
 
 
234
    node.on('click', function (e) {
 
235
        e.halt();
 
236
        var callback;
 
237
        callback = function () {
 
238
            if (subscriptions.length > 0) {
 
239
                // Fire off another unsubscribe call.
 
240
                var subscription = subscriptions.pop();
 
241
                var config = {
 
242
                    on: {success: callback,
 
243
                         failure: handler.getFailureHandler()},
 
244
                    parameters: {person: subscription.subscriber}
 
245
                };
 
246
                client.named_post(
 
247
                    subscription.bug,
 
248
                    'unsubscribe',
 
249
                    config);
 
250
            } else {
 
251
                // We are done.  Remove the parent node.
 
252
                node.replaceClass('spinner', 'remove');
 
253
                var container = node.ancestor(
 
254
                    '.subscription-description');
 
255
                var anim = Y.lazr.effects.slide_in(container);
 
256
                anim.on('end', function () {
 
257
                    container.remove();
 
258
                });
 
259
                anim.run();
 
260
            }
 
261
        };
 
262
        node.replaceClass('remove', 'spinner');
 
263
        callback();
 
264
    });
 
265
 
 
266
    return node;
 
267
 
 
268
}
 
269
namespace._get_node_for_unsubscribing = get_node_for_unsubscribing;
 
270
 
 
271
var actions = {
 
272
    CHANGE_ASSIGNEES: function () {
 
273
        return Y.Node.create('<a>Change assignees for this bug</a>')
 
274
            .set('href', LP.cache.context.web_link);
 
275
    },
 
276
    UNSUBSCRIBE_DUPLICATES: function (args) {
 
277
        var data = get_unsubscribe_duplicates_text_and_subscriptions(args);
 
278
        return get_node_for_unsubscribing(data.text, data.subscriptions);
 
279
    },
 
280
    CHANGE_TEAM_SUBSCRIPTIONS: function (args) {
 
281
        // TODO: add the ability to change notification level.
 
282
        var data = get_team_unsubscribe_text_and_subscriptions(args);
 
283
        return get_node_for_unsubscribing(data.text, data.subscriptions);
 
284
    },
 
285
    SET_BUG_SUPERVISOR: function (args) {
 
286
        return Y.Node.create('<a></a>')
 
287
            .set('text', 'Set the bug supervisor for ' + args.pillar.title)
 
288
            .set('href', args.pillar.web_link + '/+bugsupervisor');
 
289
    },
 
290
    CONTACT_TEAMS: function (args) {
 
291
        var node = Y.Node.create('<span></span>');
 
292
        node.set(
 
293
            'innerHTML',
 
294
            safely_render_description(
 
295
                {reason: 'Contact {teams} to request the administrators '+
 
296
                          'make a change',
 
297
                 vars: {
 
298
                    teams: add_url_element_to_links(
 
299
                        args.teams, '/+contactuser')}}));
 
300
        return node;
 
301
    }
 
302
};
 
303
namespace._actions = actions;
112
304
 
113
305
/**
114
306
 * Return appropriate object based on the number.
119
311
 * @param {Object} plural Object to return when number != 1.
120
312
 */
121
313
function choose_by_number(number, singular, plural) {
122
 
    if (number == 1) {
 
314
    if (number === 1) {
123
315
        return singular;
124
316
    } else {
125
317
        return plural;
139
331
 *                       references.
140
332
 */
141
333
function replace_textual_references(info, cache) {
142
 
    for (var key in info) {
143
 
        switch (typeof info[key]){
144
 
            case "object":
145
 
                replace_textual_references(info[key], cache);
146
 
                break;
147
 
            case "string":
148
 
                var ref_string = "subscription-cache-reference-";
149
 
                if (info[key].substring(0, ref_string.length) == ref_string) {
150
 
                    info[key] = cache[info[key]];
151
 
                }
152
 
                break;
153
 
            default: break;
 
334
    var key;
 
335
    for (key in info) {
 
336
        if (info.hasOwnProperty(key)) {
 
337
            switch (typeof info[key]){
 
338
                case "object":
 
339
                    replace_textual_references(info[key], cache);
 
340
                    break;
 
341
                case "string":
 
342
                    var ref_string = "subscription-cache-reference-";
 
343
                    if (info[key].substring(0, ref_string.length)
 
344
                        === ref_string) {
 
345
                        info[key] = cache[info[key]];
 
346
                    }
 
347
                break;
 
348
                default: break;
 
349
            }
154
350
        }
155
351
    }
156
352
}
166
362
        title: title,
167
363
        url: url
168
364
    };
169
 
}
 
365
};
170
366
 
171
367
/**
172
368
 * Convert a context object to a { title, url } object for use in web pages.
175
371
 */
176
372
function get_link_data(context) {
177
373
    // For testing, we take strings as well.
178
 
    if (typeof(context) == 'string') {
 
374
    if (typeof(context) === 'string') {
179
375
        return context;
180
376
    } else {
181
377
        return ObjectLink(context, context.display_name, context.web_link);
189
385
 */
190
386
function get_bug_link_data(bug) {
191
387
    // For testing, we take strings as well.
192
 
    if (typeof(bug) == 'string') {
 
388
    if (typeof(bug) === 'string') {
193
389
        return bug;
194
390
    } else {
195
391
        return ObjectLink(bug, '#' + bug.id.toString(), bug.web_link);
205
401
 * than one, variable `teams` is set containing all the teams.
206
402
 */
207
403
function gather_subscriptions_by_role(
208
 
    category, team_singular, team_plural,
209
 
    admin_team_singular, admin_team_plural) {
210
 
    var subscriptions = [];
211
 
    if (category.as_team_member.length > 0) {
212
 
        var teams = [];
213
 
        for (var index in category.as_team_member) {
214
 
            var team_subscription = category.as_team_member[index];
215
 
            teams.push(get_link_data(team_subscription.principal));
216
 
        }
217
 
        var sub = choose_by_number(
218
 
            category.as_team_member.length,
219
 
            { reason: team_singular,
220
 
              vars: {
221
 
                  team: teams[0] } },
222
 
            { reason: team_plural,
223
 
              vars: {
224
 
                  teams: teams } });
225
 
        subscriptions.push(sub);
226
 
    }
227
 
 
228
 
    if (category.as_team_admin.length > 0) {
229
 
        var teams = [];
230
 
        for (var index in category.as_team_admin) {
231
 
            var team_subscription = category.as_team_admin[index];
232
 
            teams.push(get_link_data(team_subscription.principal));
233
 
        }
234
 
        var sub = choose_by_number(
235
 
            category.as_team_admin.length,
236
 
            { reason: admin_team_singular,
237
 
              vars: {
238
 
                  team: teams[0] } },
239
 
            { reason: admin_team_plural,
240
 
              vars: {
241
 
                  teams: teams } });
242
 
        subscriptions.push(sub);
243
 
    }
244
 
 
245
 
    return subscriptions;
 
404
    category, team_config, admin_team_config) {
 
405
    var results = [],
 
406
        work_index,
 
407
        index,
 
408
        work = [{subscriptions: category.as_team_member,
 
409
                 config: team_config},
 
410
                {subscriptions: category.as_team_admin,
 
411
                 config: admin_team_config}];
 
412
    for (work_index = 0; work_index < work.length; work_index++) {
 
413
        var subscriptions = work[work_index].subscriptions;
 
414
        var config = work[work_index].config;
 
415
        if (subscriptions.length > 0) {
 
416
            var team_map = {};
 
417
            var teams = [];
 
418
            for (index = 0; index < subscriptions.length; index++) {
 
419
                var team_subscription = subscriptions[index],
 
420
                    team = team_subscription.principal,
 
421
                    key = team.web_link;
 
422
                key = Y.Lang.isValue(key) ? key : team; // For tests.
 
423
                if (!Y.Lang.isValue(team_map[key])) {
 
424
                    var link_data = get_link_data(team);
 
425
                    team_map[team.web_link] = link_data;
 
426
                    teams.push(link_data);
 
427
                }
 
428
            }
 
429
            var sub = choose_by_number(
 
430
                subscriptions.length,
 
431
                { reason: config.singular,
 
432
                  vars: {
 
433
                      team: teams[0] } },
 
434
                { reason: config.plural,
 
435
                  vars: {
 
436
                      teams: teams } });
 
437
            sub.action = config.action;
 
438
            sub.args = {teams: teams};
 
439
            results.push(sub);
 
440
        }
 
441
    }
 
442
 
 
443
    return results;
246
444
}
247
445
 
248
446
/**
255
453
    if (category.personal.length > 0) {
256
454
        subscriptions.push(
257
455
            { reason: reasons.YOU_ASSIGNED,
258
 
              vars: {} });
 
456
              vars: {},
 
457
              action: actions.CHANGE_ASSIGNEES });
259
458
    }
260
459
 
261
460
    // We add all the team assignments grouped by roles in the team.
262
461
    return subscriptions.concat(
263
462
        gather_subscriptions_by_role(
264
 
            category, reasons.TEAM_ASSIGNED, reasons.TEAMS_ASSIGNED,
265
 
            reasons.ADMIN_TEAM_ASSIGNED, reasons.ADMIN_TEAMS_ASSIGNED));
 
463
            category,
 
464
            {singular: reasons.TEAM_ASSIGNED,
 
465
             plural: reasons.TEAMS_ASSIGNED,
 
466
             action: actions.CONTACT_TEAMS},
 
467
            {singular: reasons.ADMIN_TEAM_ASSIGNED,
 
468
             plural: reasons.ADMIN_TEAMS_ASSIGNED,
 
469
             action: actions.CHANGE_ASSIGNEES}));
266
470
}
267
471
namespace._gather_subscriptions_as_assignee =
268
472
        gather_subscriptions_as_assignee;
269
473
 
270
474
/**
 
475
 * Adds a `subscription` to `subscriptions` if it's not in the list already.
 
476
 * Compares reason, action and all the `vars` from existing subscription.
 
477
 */
 
478
function add_subscription_to_set(subscriptions, subscription) {
 
479
    var index, sub;
 
480
    for (index = 0; index < subscriptions.length; index++) {
 
481
        sub = subscriptions[index];
 
482
        if (sub.reason === subscription.reason &&
 
483
            sub.action === subscription.action) {
 
484
            var are_vars_same = true;
 
485
            var param;
 
486
            for (param in sub.vars) {
 
487
                if (sub.vars.hasOwnProperty(param)) {
 
488
                    // We only check vars from the existing subscription.
 
489
                    // Theoretically, there could be a var on `subscription`
 
490
                    // not present on `sub`, but we're guarding against that
 
491
                    // with reason/action checks.
 
492
                    if (sub.vars[param].self
 
493
                            !== subscription.vars[param].self) {
 
494
                        are_vars_same = false;
 
495
                        break;
 
496
                    }
 
497
                }
 
498
            }
 
499
            if (are_vars_same) {
 
500
                return;
 
501
            }
 
502
        }
 
503
    }
 
504
    // We haven't found matching subscriptions, add it.
 
505
    subscriptions.push(subscription);
 
506
}
 
507
 
 
508
/**
271
509
 * Gather subscription information for implicit bug supervisor.
272
510
 */
273
511
function gather_subscriptions_as_supervisor(category) {
274
512
    var subscriptions = [];
275
513
    var reasons = namespace._reasons;
 
514
    var index, team_subscription, team_link;
276
515
 
277
 
    for (var index in category.personal) {
 
516
    for (index = 0; index < category.personal.length; index++) {
278
517
        var subscription = category.personal[index];
279
 
        subscriptions.push({
 
518
        add_subscription_to_set(subscriptions, {
280
519
            reason: reasons.YOU_OWNER,
281
520
            vars: {
282
521
                pillar: get_link_data(subscription.pillar)
283
 
            }
 
522
            },
 
523
            action: actions.SET_BUG_SUPERVISOR,
 
524
            args: {pillar: subscription.pillar}
284
525
        });
285
526
    }
286
527
 
287
 
    for (var index in category.as_team_member) {
288
 
        var team_subscription = category.as_team_member[index];
289
 
        subscriptions.push({
 
528
    for (index = 0; index < category.as_team_member.length; index++) {
 
529
        team_subscription = category.as_team_member[index];
 
530
        team_link = get_link_data(team_subscription.principal);
 
531
        add_subscription_to_set(subscriptions, {
290
532
            reason: reasons.TEAM_OWNER,
291
533
            vars: {
292
 
                team: get_link_data(team_subscription.principal),
 
534
                team: team_link,
293
535
                pillar: get_link_data(team_subscription.pillar)
294
 
            }
 
536
            },
 
537
            action: actions.CONTACT_TEAMS,
 
538
            args: {teams: [team_link]}
295
539
        });
296
540
    }
297
541
 
298
 
    for (var index in category.as_team_admin) {
299
 
        var team_subscription = category.as_team_admin[index];
300
 
        subscriptions.push({
 
542
    for (index = 0; index < category.as_team_admin.length; index++) {
 
543
        team_subscription = category.as_team_admin[index];
 
544
        add_subscription_to_set(subscriptions, {
301
545
            reason: reasons.ADMIN_TEAM_OWNER,
302
546
            vars: {
303
547
                team: get_link_data(team_subscription.principal),
304
548
                pillar: get_link_data(team_subscription.pillar)
305
 
            }
 
549
            },
 
550
            action: actions.SET_BUG_SUPERVISOR,
 
551
            args: {pillar: team_subscription.pillar}
306
552
        });
307
553
    }
308
554
 
312
558
        gather_subscriptions_as_supervisor;
313
559
 
314
560
function gather_dupe_subscriptions_by_team(team_subscriptions,
315
 
                                           singular, plural) {
 
561
                                           singular, plural, action) {
316
562
    var subscriptions = [];
 
563
    var index;
 
564
    var subscription, sub;
 
565
    var added_bug;
 
566
    var team_dupes_idx, team_dupes;
317
567
 
318
568
    // Collated list of { team: ..., bugs: []} records.
319
569
    var dupes_by_teams = [];
320
 
    for (var index in team_subscriptions) {
321
 
        var subscription = team_subscriptions[index];
 
570
    for (index = 0; index < team_subscriptions.length; index++) {
 
571
        subscription = team_subscriptions[index];
322
572
        // Find the existing team reference.
323
 
        var added_bug = false;
324
 
        for (var team_dupes_idx in dupes_by_teams) {
325
 
            var team_dupes = dupes_by_teams[team_dupes_idx];
326
 
            if (team_dupes.team == subscription.principal) {
 
573
        added_bug = false;
 
574
        for (team_dupes_idx = 0; team_dupes_idx < dupes_by_teams.length;
 
575
             team_dupes_idx++) {
 
576
            team_dupes = dupes_by_teams[team_dupes_idx];
 
577
            if (team_dupes.team === subscription.principal) {
327
578
                team_dupes.bugs.push(get_bug_link_data(subscription.bug));
328
579
                added_bug = true;
329
580
                break;
336
587
            });
337
588
        }
338
589
    }
339
 
    for (var team_dupes_idx in dupes_by_teams) {
340
 
        var team_dupes = dupes_by_teams[team_dupes_idx];
341
 
        var sub = choose_by_number(
 
590
    for (team_dupes_idx = 0; team_dupes_idx < dupes_by_teams.length;
 
591
         team_dupes_idx++) {
 
592
        team_dupes = dupes_by_teams[team_dupes_idx];
 
593
        sub = choose_by_number(
342
594
            team_dupes.bugs.length,
343
595
            { reason: singular,
344
596
              vars: { duplicate_bug: team_dupes.bugs[0],
346
598
            { reason: plural,
347
599
              vars: { duplicate_bugs: team_dupes.bugs,
348
600
                      team: get_link_data(team_dupes.team) }});
 
601
        sub.action = action;
 
602
        sub.args = { teams: [sub.vars.team],
 
603
                     bugs: team_dupes.bugs };
349
604
        subscriptions.push(sub);
350
605
    }
351
606
    return subscriptions;
357
612
function gather_subscriptions_from_duplicates(category) {
358
613
    var subscriptions = [];
359
614
    var reasons = namespace._reasons;
 
615
    var index, dupes, subscription;
360
616
 
361
617
    if (category.personal.length > 0) {
362
 
        var dupes = [];
363
 
        for (var index in category.personal) {
364
 
            var subscription = category.personal[index];
 
618
        dupes = [];
 
619
        for (index = 0; index < category.personal.length; index++) {
 
620
            subscription = category.personal[index];
365
621
            dupes.push(
366
622
                get_bug_link_data(subscription.bug));
367
623
        }
371
627
              vars: { duplicate_bug: dupes[0] }},
372
628
            { reason: reasons.YOU_SUBSCRIBED_TO_DUPLICATES,
373
629
              vars: { duplicate_bugs: dupes }});
 
630
        sub.action = actions.UNSUBSCRIBE_DUPLICATES;
 
631
        sub.args = { bugs: dupes };
374
632
        subscriptions.push(sub);
375
633
    }
376
634
 
379
637
        gather_dupe_subscriptions_by_team(
380
638
            category.as_team_member,
381
639
            reasons.TEAM_SUBSCRIBED_TO_DUPLICATE,
382
 
            reasons.TEAM_SUBSCRIBED_TO_DUPLICATES));
 
640
            reasons.TEAM_SUBSCRIBED_TO_DUPLICATES,
 
641
            actions.CONTACT_TEAMS));
383
642
 
384
643
    // Get subscriptions as team admin, grouped by teams.
385
644
    subscriptions = subscriptions.concat(
386
645
        gather_dupe_subscriptions_by_team(
387
646
            category.as_team_admin,
388
647
            reasons.ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATE,
389
 
            reasons.ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATES));
 
648
            reasons.ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATES,
 
649
            actions.UNSUBSCRIBE_DUPLICATES));
390
650
 
391
651
    return subscriptions;
392
652
}
399
659
function gather_subscriptions_through_team(category) {
400
660
    var reasons = namespace._reasons;
401
661
    return gather_subscriptions_by_role(
402
 
        category, reasons.TEAM_SUBSCRIBED, reasons.TEAMS_SUBSCRIBED,
403
 
        reasons.ADMIN_TEAM_SUBSCRIBED, reasons.ADMIN_TEAMS_SUBSCRIBED);
 
662
        category,
 
663
        {singular: reasons.TEAM_SUBSCRIBED,
 
664
         plural: reasons.TEAMS_SUBSCRIBED,
 
665
         action: actions.CONTACT_TEAMS},
 
666
        {singular: reasons.ADMIN_TEAM_SUBSCRIBED,
 
667
         plural:reasons.ADMIN_TEAMS_SUBSCRIBED,
 
668
         action: actions.CHANGE_TEAM_SUBSCRIPTIONS});
404
669
}
405
670
namespace._gather_subscriptions_through_team =
406
671
        gather_subscriptions_through_team;
425
690
function get_direct_subscription_information(info) {
426
691
    var reason;
427
692
    var reasons = namespace._reasons;
428
 
    if (info.count == 0) {
 
693
    if (info.count === 0) {
429
694
        reason = reasons.NOT_SUBSCRIBED;
430
695
    } else if (info.muted) {
431
696
        reason = reasons.MUTED_SUBSCRIPTION;
439
704
        var bug = subscription.bug;
440
705
        if (subscription.principal_is_reporter) {
441
706
            reason = reasons.YOU_REPORTED;
442
 
        } else if (bug.private) {
 
707
        } else if (bug['private']) {
443
708
            reason = reasons.YOU_SUBSCRIBED_BUG_SUPERVISOR;
444
709
        } else if (bug.security_related) {
445
710
            // If bug is both private and security-related, you'll
472
737
    if (Y.Lang.isString(element)) {
473
738
        return element;
474
739
    } else if (Y.Lang.isObject(element)) {
475
 
        if (element.url === undefined && element.title == undefined) {
 
740
        if (element.url === undefined && element.title === undefined) {
476
741
            Y.error('Not a proper ObjectLink.');
477
742
        }
478
743
        var node = Y.Node.create('<div></div>');
491
756
 * Array sort function for objects sorting them by their `title` property.
492
757
 */
493
758
function sort_by_title(a, b) {
494
 
    return ((a.title == b.title) ? 0 :
 
759
    return ((a.title === b.title) ? 0 :
495
760
            ((a.title > b.title) ? 1 : -1));
496
761
}
497
762
 
508
773
 */
509
774
function safely_render_description(subscription, additional_vars) {
510
775
    function var_replacer(key, vars) {
 
776
        var index, final_element, text_elements;
511
777
        if (vars !== undefined) {
512
778
            if (Y.Lang.isArray(vars)) {
513
779
                vars.sort(sort_by_title);
514
 
                // We want a plural concatenation.
515
 
                var final_element = vars.pop();
516
 
                var text_elements = [];
517
 
                for (var index in vars) {
518
 
                    text_elements.push(get_objectlink_html(vars[index]));
519
 
                };
520
 
                return (text_elements.join(', ') +
521
 
                        ' and ' + get_objectlink_html(final_element));
 
780
                // This can handle plural or singular.
 
781
                final_element = get_objectlink_html(vars.pop());
 
782
                text_elements = [];
 
783
                for (index in vars) {
 
784
                    if (vars.hasOwnProperty(index)) {
 
785
                        text_elements.push(get_objectlink_html(vars[index]));
 
786
                    }
 
787
                }
 
788
                if (text_elements.length > 0) {
 
789
                    return text_elements.join(', ') + ' and ' + final_element;
 
790
                } else {
 
791
                    return final_element;
 
792
                }
522
793
            } else {
523
794
                return get_objectlink_html(vars);
524
795
            }
563
834
function get_single_description_node(subscription, extra_data) {
564
835
    var node = Y.Node.create('<div></div>')
565
836
        .addClass('subscription-description');
 
837
    var action_node = subscription.action(subscription.args);
 
838
    if (Y.Lang.isValue(action_node)) {
 
839
        node.appendChild(action_node.setStyle('float', 'right'));
 
840
    }
566
841
    node.appendChild(
567
842
        Y.Node.create('<div></div>')
568
843
            .addClass('description-text')
584
859
 */
585
860
function get_other_descriptions_node(info, extra_data) {
586
861
    var subs = gather_nondirect_subscriptions(info);
587
 
    if (subs.length > 0) {
588
 
        var other_node = Y.Node.create('<div></div>')
 
862
    var index;
 
863
    if (subs.length > 0 || has_structural_subscriptions()) {
 
864
        var node = Y.Node.create('<div></div>')
589
865
            .set('id', 'other-subscriptions');
590
 
 
591
 
        for (var index in subs) {
592
 
            other_node.appendChild(
 
866
        var header = Y.Node.create('<div></div>')
 
867
            .set('id', 'other-subscriptions-header');
 
868
        var header_link = Y.Node.create('<a></a>')
 
869
            .set('href', '#')
 
870
            .set('text', 'Other subscriptions');
 
871
        header.appendChild(header_link);
 
872
        node.appendChild(header);
 
873
        var list = Y.Node.create('<div></div>')
 
874
            .set('id', 'other-subscriptions-list');
 
875
        node.appendChild(list);
 
876
 
 
877
        setup_slider(list, header_link);
 
878
 
 
879
        for (index = 0; index < subs.length; index++) {
 
880
            list.appendChild(
593
881
                get_single_description_node(subs[index], extra_data));
594
882
        }
595
883
 
596
 
        return other_node;
 
884
        return node;
597
885
    } else {
598
886
        return undefined;
599
887
    }
600
888
}
601
889
namespace._get_other_descriptions_node = get_other_descriptions_node;
602
890
 
 
891
/**
 
892
 * Are there any structural subscriptions that need to be rendered.
 
893
 */
 
894
function has_structural_subscriptions() {
 
895
    return (LP.cache.subscription_info &&
 
896
            LP.cache.subscription_info.length > 0);
 
897
}
 
898
 
 
899
/**
 
900
 * Sets up a slider that slides the `body` in and out when `header`
 
901
 * is clicked.
 
902
 */
 
903
function setup_slider(body, header) {
 
904
    // Hide the widget body contents.
 
905
    body.addClass('lazr-closed');
 
906
    body.setStyle('display', 'none');
 
907
 
 
908
    // Ensure that the widget header uses the correct sprite icon
 
909
    // and gets the styling for javascript actions applied.
 
910
    header.addClass('sprite');
 
911
    header.addClass('treeCollapsed');
 
912
    header.addClass('js-action');
 
913
 
 
914
    var slide;
 
915
    function toggle_body_visibility(e) {
 
916
        e.halt();
 
917
        if (!slide) {
 
918
            slide = Y.lazr.effects.slide_out(body);
 
919
            header.replaceClass('treeCollapsed', 'treeExpanded');
 
920
        } else {
 
921
            slide.set('reverse', !slide.get('reverse'));
 
922
            header.toggleClass('treeExpanded');
 
923
            header.toggleClass('treeCollapsed');
 
924
        }
 
925
        slide.stop();
 
926
        slide.run();
 
927
    }
 
928
    header.on('click', toggle_body_visibility);
 
929
}
603
930
 
604
931
/**
605
932
 * Add descriptions for all non-structural subscriptions to the page.
616
943
    replace_textual_references(info, LP.cache);
617
944
 
618
945
    var extra_data = {
619
 
        bug_id: '#' + info.bug_id.toString(),
 
946
        bug_id: '#' + info.bug_id.toString()
620
947
    };
621
948
 
622
949
    var content_node = Y.one(config.description_box);
629
956
        content_node.appendChild(other_node);
630
957
    }
631
958
}
632
 
namespace.show_subscription_description = show_subscription_description
 
959
namespace.show_subscription_description = show_subscription_description;
633
960
 
634
961
}, '0.1', {requires: [
635
 
    'dom', 'node', 'substitute'
 
962
    'dom', 'event', 'node', 'substitute', 'lazr.effects', 'lp.app.errors',
 
963
    'lp.client'
636
964
]});