~launchpad-pqm/launchpad/devel

12556.6.1 by Danilo Segan
Add the subscription info JS.
1
/* Copyright 2011 Canonical Ltd.  This software is licensed under the
2
 * GNU Affero General Public License version 3 (see the file LICENSE).
3
 *
4
 * Provide information and actions on all bug subscriptions a person holds.
5
 *
6
 * @module bugs
7
 * @submodule subscription
8
 */
9
10
YUI.add('lp.bugs.subscription', function(Y) {
11
12
var namespace = Y.namespace('lp.bugs.subscription');
13
14
/**
15
 * These are the descriptions strings of what might be the cause of you
16
 * getting an email.
17
 */
18
19
var _BECAUSE_YOU_ARE = 'You receive emails about this bug because you are ';
20
12556.6.3 by Danilo Segan
Remove unneeded reasons, prefer direct subscription over the assignee.
21
/**
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
22
 * Store complete subscription 'reasons' for easier overriding and testing.
12556.6.3 by Danilo Segan
Remove unneeded reasons, prefer direct subscription over the assignee.
23
 *
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
24
 * Other 'reasons' are added to the object as required string components
25
 * are defined.
12556.6.3 by Danilo Segan
Remove unneeded reasons, prefer direct subscription over the assignee.
26
 */
27
var reasons = {
28
    NOT_SUBSCRIBED: "You are not subscribed to this bug.",
12556.7.13 by Danilo Segan
Simplify direct subscription info message.
29
    NOT_PERSONALLY_SUBSCRIBED: (
30
        "You are not directly subscribed to this bug, " +
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
31
            "but you have other subscriptions."),
7675.1160.7 by Gary Poster
use mute icons; fix subscription.js to handle new mute behavior along the way.
32
    MUTED_SUBSCRIPTION: "You have muted all your direct email from this bug."
12556.6.3 by Danilo Segan
Remove unneeded reasons, prefer direct subscription over the assignee.
33
};
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
34
namespace._reasons = reasons;
35
12556.6.1 by Danilo Segan
Add the subscription info JS.
36
/* These are components for team participation. */
13114.1.1 by Brad Crittenden
Use display:table-cell formatting to ensure proper spacing.
37
var _OF_TEAM = 'of the team {team}, which is ';
38
var _OF_TEAMS = 'of the teams {teams}, which are ';
12556.6.1 by Danilo Segan
Add the subscription info JS.
39
var _BECAUSE_TEAM_IS = _BECAUSE_YOU_ARE + 'a member ' + _OF_TEAM;
40
var _ADMIN_BECAUSE_TEAM_IS = (
41
    _BECAUSE_YOU_ARE + 'a member and administrator ' + _OF_TEAM);
42
var _BECAUSE_TEAMS_ARE = _BECAUSE_YOU_ARE + 'a member ' + _OF_TEAMS;
43
var _ADMIN_BECAUSE_TEAMS_ARE = (
44
        _BECAUSE_YOU_ARE + 'a member and administrator ' + _OF_TEAMS);
45
46
/* These are the assignment variations. */
47
var _ASSIGNED = 'assigned to work on it.';
48
/* These are the actual strings to use. */
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
49
Y.mix(reasons, {
50
    YOU_ASSIGNED: _BECAUSE_YOU_ARE + _ASSIGNED,
51
    TEAM_ASSIGNED: _BECAUSE_TEAM_IS + _ASSIGNED,
52
    ADMIN_TEAM_ASSIGNED: _ADMIN_BECAUSE_TEAM_IS + _ASSIGNED,
53
    TEAMS_ASSIGNED: _BECAUSE_TEAMS_ARE + _ASSIGNED,
54
    ADMIN_TEAMS_ASSIGNED: _ADMIN_BECAUSE_TEAMS_ARE + _ASSIGNED
55
});
12556.6.1 by Danilo Segan
Add the subscription info JS.
56
57
/* These are the direct subscription variations. */
58
var _SUBSCRIBED = 'directly subscribed to it.';
59
var _MAY_HAVE_BEEN_CREATED = ' This subscription may have been created ';
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
60
var _YOU_SUBSCRIBED = _BECAUSE_YOU_ARE + _SUBSCRIBED;
61
12556.6.1 by Danilo Segan
Add the subscription info JS.
62
/* Now these are the actual options we use. */
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
63
Y.mix(reasons, {
64
    YOU_SUBSCRIBED: _YOU_SUBSCRIBED,
65
    YOU_REPORTED: (_YOU_SUBSCRIBED + _MAY_HAVE_BEEN_CREATED +
66
                    'when you reported the bug.'),
67
    YOU_SUBSCRIBED_BUG_SUPERVISOR: (
68
        _YOU_SUBSCRIBED + _MAY_HAVE_BEEN_CREATED +
69
            'because the bug was private and you are a bug supervisor.'),
70
    YOU_SUBSCRIBED_SECURITY_CONTACT: (
71
        _YOU_SUBSCRIBED + _MAY_HAVE_BEEN_CREATED +
72
            'because the bug was security related and you are ' +
73
            'a security contact.'),
74
    TEAM_SUBSCRIBED: _BECAUSE_TEAM_IS + _SUBSCRIBED,
75
    ADMIN_TEAM_SUBSCRIBED: _ADMIN_BECAUSE_TEAM_IS + _SUBSCRIBED,
76
    TEAMS_SUBSCRIBED: _BECAUSE_TEAMS_ARE + _SUBSCRIBED,
77
    ADMIN_TEAMS_SUBSCRIBED: _ADMIN_BECAUSE_TEAMS_ARE + _SUBSCRIBED
78
});
12556.6.1 by Danilo Segan
Add the subscription info JS.
79
80
/* These are the duplicate bug variations. */
81
var _SUBSCRIBED_TO_DUPLICATE = (
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
82
    'a direct subscriber to bug {duplicate_bug}, which is marked as a ' +
12556.7.17 by Danilo Segan
Expose bug_id as well.
83
        'duplicate of this bug, {bug_id}.');
12556.6.1 by Danilo Segan
Add the subscription info JS.
84
var _SUBSCRIBED_TO_DUPLICATES = (
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
85
    'a direct subscriber to bugs {duplicate_bugs}, which are marked as ' +
12556.7.17 by Danilo Segan
Expose bug_id as well.
86
        'duplicates of this bug, {bug_id}.');
12556.6.1 by Danilo Segan
Add the subscription info JS.
87
/* These are the actual strings to use. */
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
88
Y.mix(reasons, {
89
    YOU_SUBSCRIBED_TO_DUPLICATE: _BECAUSE_YOU_ARE + _SUBSCRIBED_TO_DUPLICATE,
90
    YOU_SUBSCRIBED_TO_DUPLICATES: (
91
        _BECAUSE_YOU_ARE + _SUBSCRIBED_TO_DUPLICATES),
92
    TEAM_SUBSCRIBED_TO_DUPLICATE: _BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATE,
93
    TEAM_SUBSCRIBED_TO_DUPLICATES: (
94
        _BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATES),
95
    ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATE: (
96
        _ADMIN_BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATE),
97
    ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATES: (
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
98
        _ADMIN_BECAUSE_TEAM_IS + _SUBSCRIBED_TO_DUPLICATES)
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
99
});
12556.6.1 by Danilo Segan
Add the subscription info JS.
100
101
/* These are the owner variations. */
102
var _OWNER = (
12556.8.4 by Danilo Segan
Put structural subscriptions inside 'other subscriptions' slide-out.
103
    "the owner of {pillar}, which has no bug supervisor.");
12556.6.1 by Danilo Segan
Add the subscription info JS.
104
/* These are the actual strings to use. */
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
105
Y.mix(reasons, {
106
    YOU_OWNER: _BECAUSE_YOU_ARE + _OWNER,
107
    TEAM_OWNER: _BECAUSE_TEAM_IS + _OWNER,
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
108
    ADMIN_TEAM_OWNER: _ADMIN_BECAUSE_TEAM_IS + _OWNER
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
109
});
12556.6.1 by Danilo Segan
Add the subscription info JS.
110
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
111
/* These are the actions */
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
112
12830.2.1 by Gary Poster
tests for add_url_element_to_links
113
/**
12830.1.13 by Danilo Segan
Merge with Gary's branch.
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
 */
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
118
function add_url_element_to_links(links, url_suffix) {
119
    var result = [];
12830.1.15 by Danilo Segan
Move vars to the top of functions.
120
    var index;
12830.1.17 by Danilo Segan
Test failure detected a typo.
121
    for (index = 0; index < links.length; index++) {
12830.2.1 by Gary Poster
tests for add_url_element_to_links
122
        var original = links[index];
123
        result.push(ObjectLink(
124
            original.self, original.title, original.url + url_suffix));
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
125
    }
126
    return result;
127
}
12830.2.1 by Gary Poster
tests for add_url_element_to_links
128
namespace._add_url_element_to_links = add_url_element_to_links;
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
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
12830.1.9 by Danilo Segan
Move string construction and subscriptions list construction into the separate method for unit-testing.
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 = [];
12830.1.15 by Danilo Segan
Move vars to the top of functions.
146
    var index;
12830.1.9 by Danilo Segan
Move string construction and subscriptions list construction into the separate method for unit-testing.
147
    if (Y.Lang.isValue(args.teams)) {
148
        // Unsubscribe team.
149
150
        // There should never be more than one team.
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
151
        if (args.teams.length !== 1) {
12830.1.9 by Danilo Segan
Move string construction and subscriptions list construction into the separate method for unit-testing.
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.
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
157
        for (index = 0; index < args.bugs.length; index++) {
12830.1.9 by Danilo Segan
Move string construction and subscriptions list construction into the separate method for unit-testing.
158
            subscriptions.push({
159
                subscriber: args.teams[0].self.self_link,
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
160
                bug: args.bugs[index].self.self_link
12830.1.9 by Danilo Segan
Move string construction and subscriptions list construction into the separate method for unit-testing.
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.
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
172
        for (index = 0; index < args.bugs.length; index++) {
12830.1.9 by Danilo Segan
Move string construction and subscriptions list construction into the separate method for unit-testing.
173
            subscriptions.push({
174
                subscriber: LP.links.me,
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
175
                bug: args.bugs[index].self.self_link
12830.1.9 by Danilo Segan
Move string construction and subscriptions list construction into the separate method for unit-testing.
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
12830.1.11 by Danilo Segan
Share more of the code for unsubscribe dupes and change team subs actions.
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 = [];
12830.1.15 by Danilo Segan
Move vars to the top of functions.
197
    var index;
12830.1.11 by Danilo Segan
Share more of the code for unsubscribe dupes and change team subs actions.
198
    var text = choose_by_number(args.teams.length,
199
                                'Unsubscribe this team',
200
                                'Unsubscribe all of these teams');
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
201
    for (index = 0; index < args.teams.length; index++) {
12830.1.11 by Danilo Segan
Share more of the code for unsubscribe dupes and change team subs actions.
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
12830.1.12 by Danilo Segan
Add a test for set-bug-supervisor action.
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
 */
12830.1.11 by Danilo Segan
Share more of the code for unsubscribe dupes and change team subs actions.
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();
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
236
        var callback;
237
        callback = function () {
12830.1.11 by Danilo Segan
Share more of the code for unsubscribe dupes and change team subs actions.
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
            }
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
261
        };
12830.1.11 by Danilo Segan
Share more of the code for unsubscribe dupes and change team subs actions.
262
        node.replaceClass('remove', 'spinner');
263
        callback();
264
    });
265
266
    return node;
267
268
}
12830.2.2 by Gary Poster
tests for get_node_for_unsubscribing
269
namespace._get_node_for_unsubscribing = get_node_for_unsubscribing;
12830.1.11 by Danilo Segan
Share more of the code for unsubscribe dupes and change team subs actions.
270
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
271
var actions = {
272
    CHANGE_ASSIGNEES: function () {
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
273
        return Y.Node.create('<a>Change assignees for this bug</a>')
12830.1.6 by Danilo Segan
Fix tests, add one for change assignees.
274
            .set('href', LP.cache.context.web_link);
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
275
    },
12830.1.4 by Danilo Segan
Add unsubscribe-duplicates code.
276
    UNSUBSCRIBE_DUPLICATES: function (args) {
12830.1.10 by Danilo Segan
Use the new method.
277
        var data = get_unsubscribe_duplicates_text_and_subscriptions(args);
12830.1.11 by Danilo Segan
Share more of the code for unsubscribe dupes and change team subs actions.
278
        return get_node_for_unsubscribing(data.text, data.subscriptions);
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
279
    },
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
280
    CHANGE_TEAM_SUBSCRIPTIONS: function (args) {
281
        // TODO: add the ability to change notification level.
12830.1.11 by Danilo Segan
Share more of the code for unsubscribe dupes and change team subs actions.
282
        var data = get_team_unsubscribe_text_and_subscriptions(args);
283
        return get_node_for_unsubscribing(data.text, data.subscriptions);
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
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)
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
288
            .set('href', args.pillar.web_link + '/+bugsupervisor');
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
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 '+
13100.2.1 by William Grant
Revert r13092. It is possibly bad.
296
                          'make a change',
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
297
                 vars: {
298
                    teams: add_url_element_to_links(
299
                        args.teams, '/+contactuser')}}));
300
        return node;
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
301
    }
302
};
303
namespace._actions = actions;
12556.6.1 by Danilo Segan
Add the subscription info JS.
304
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
305
/**
12556.6.12 by Danilo Segan
Rename choose_string_by_number to choose_by_number since it works with objects just as well.
306
 * Return appropriate object based on the number.
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
307
 *
12556.6.12 by Danilo Segan
Rename choose_string_by_number to choose_by_number since it works with objects just as well.
308
 * @method choose_by_number.
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
309
 * @param {Integer} number Number used in the string.
12556.6.12 by Danilo Segan
Rename choose_string_by_number to choose_by_number since it works with objects just as well.
310
 * @param {Object} singular Object to return when number == 1.
311
 * @param {Object} plural Object to return when number != 1.
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
312
 */
12556.6.12 by Danilo Segan
Rename choose_string_by_number to choose_by_number since it works with objects just as well.
313
function choose_by_number(number, singular, plural) {
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
314
    if (number === 1) {
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
315
        return singular;
316
    } else {
317
        return plural;
318
    }
12556.6.5 by Danilo Segan
Minor cleanups.
319
}
12556.6.12 by Danilo Segan
Rename choose_string_by_number to choose_by_number since it works with objects just as well.
320
namespace._choose_by_number = choose_by_number;
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
321
12556.6.6 by Danilo Segan
Add replace_textual_references for cache processing.
322
/**
323
 * Replaces textual references in `info` with actual objects from `cache`.
324
 *
325
 * This assumes that object references are specified with strings
326
 * starting with 'subscription-cache-reference', and are direct keys
327
 * for objects in `cache`.
328
 *
329
 * @param {Object} info Object to recursively look for references through.
330
 * @param {Object} cache Cache containing the objects indexed by their
331
 *                       references.
332
 */
333
function replace_textual_references(info, cache) {
12830.1.15 by Danilo Segan
Move vars to the top of functions.
334
    var key;
335
    for (key in info) {
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
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
            }
12556.6.6 by Danilo Segan
Add replace_textual_references for cache processing.
350
        }
351
    }
352
}
353
namespace._replace_textual_references = replace_textual_references;
354
12556.6.7 by Danilo Segan
Start gathering subscription info.
355
/**
12556.6.16 by Danilo Segan
Return link data in standard form if possible.
356
 * ObjectLink class to unify link elements for better consistency.
357
 * Needed because some objects expose `title`, others expose `display_name`.
358
 */
12556.6.18 by Danilo Segan
Add tests for supervisor.
359
ObjectLink = function(self, title, url) {
12556.6.16 by Danilo Segan
Return link data in standard form if possible.
360
    return {
12556.6.18 by Danilo Segan
Add tests for supervisor.
361
        self: self,
12556.6.16 by Danilo Segan
Return link data in standard form if possible.
362
        title: title,
363
        url: url
364
    };
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
365
};
12556.6.16 by Danilo Segan
Return link data in standard form if possible.
366
367
/**
12556.6.18 by Danilo Segan
Add tests for supervisor.
368
 * Convert a context object to a { title, url } object for use in web pages.
369
 * Uses `display_name` and `web_link` attributes.
12556.6.16 by Danilo Segan
Return link data in standard form if possible.
370
 * Additionally, accepts a string as well and returns it unmodified.
371
 */
12556.6.18 by Danilo Segan
Add tests for supervisor.
372
function get_link_data(context) {
12556.6.16 by Danilo Segan
Return link data in standard form if possible.
373
    // For testing, we take strings as well.
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
374
    if (typeof(context) === 'string') {
12556.6.18 by Danilo Segan
Add tests for supervisor.
375
        return context;
12556.6.16 by Danilo Segan
Return link data in standard form if possible.
376
    } else {
12556.6.18 by Danilo Segan
Add tests for supervisor.
377
        return ObjectLink(context, context.display_name, context.web_link);
12556.6.16 by Danilo Segan
Return link data in standard form if possible.
378
    }
379
}
380
381
/**
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
382
 * Convert a bug object to a { title, url } object for use in web pages.
383
 * Uses `id` and `web_link` attributes.
384
 * Additionally, accepts a string as well and returns it unmodified.
385
 */
386
function get_bug_link_data(bug) {
387
    // For testing, we take strings as well.
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
388
    if (typeof(bug) === 'string') {
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
389
        return bug;
390
    } else {
12556.7.9 by Danilo Segan
Improve safely_render_description.
391
        return ObjectLink(bug, '#' + bug.id.toString(), bug.web_link);
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
392
    }
393
}
394
395
/**
12556.6.23 by Danilo Segan
Refactor to introduce gather_subscriptions_by_role common to team-subs and assignment.
396
 * Gather all team subscriptions and sort them by the role: member/admin.
397
 * Returns up to 2 different subscription records, one for all teams
398
 * a person is a member of, and another for all teams a person is
399
 * an admin for.
400
 * With one team in a subscription, variable `team` is set, and with more
401
 * than one, variable `teams` is set containing all the teams.
402
 */
403
function gather_subscriptions_by_role(
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
404
    category, team_config, admin_team_config) {
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
405
    var results = [],
12830.1.15 by Danilo Segan
Move vars to the top of functions.
406
        work_index,
407
        index,
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
408
        work = [{subscriptions: category.as_team_member,
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
409
                 config: team_config},
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
410
                {subscriptions: category.as_team_admin,
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
411
                 config: admin_team_config}];
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
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;
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
415
        if (subscriptions.length > 0) {
416
            var team_map = {};
417
            var teams = [];
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
418
            for (index = 0; index < subscriptions.length; index++) {
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
419
                var team_subscription = subscriptions[index],
420
                    team = team_subscription.principal,
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
421
                    key = team.web_link;
422
                key = Y.Lang.isValue(key) ? key : team; // For tests.
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
423
                if (!Y.Lang.isValue(team_map[key])) {
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
424
                    var link_data = get_link_data(team);
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
425
                    team_map[team.web_link] = link_data;
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
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 } });
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
437
            sub.action = config.action;
438
            sub.args = {teams: teams};
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
439
            results.push(sub);
440
        }
441
    }
442
443
    return results;
12556.6.23 by Danilo Segan
Refactor to introduce gather_subscriptions_by_role common to team-subs and assignment.
444
}
445
446
/**
12556.6.7 by Danilo Segan
Start gathering subscription info.
447
 * Gather subscription information for assignee.
448
 */
449
function gather_subscriptions_as_assignee(category) {
450
    var subscriptions = [];
12556.6.8 by Danilo Segan
Add personal assignment support.
451
    var reasons = namespace._reasons;
452
453
    if (category.personal.length > 0) {
454
        subscriptions.push(
455
            { reason: reasons.YOU_ASSIGNED,
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
456
              vars: {},
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
457
              action: actions.CHANGE_ASSIGNEES });
12556.6.8 by Danilo Segan
Add personal assignment support.
458
    }
12556.6.7 by Danilo Segan
Start gathering subscription info.
459
12556.6.23 by Danilo Segan
Refactor to introduce gather_subscriptions_by_role common to team-subs and assignment.
460
    // We add all the team assignments grouped by roles in the team.
461
    return subscriptions.concat(
462
        gather_subscriptions_by_role(
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
463
            category,
464
            {singular: reasons.TEAM_ASSIGNED,
465
             plural: reasons.TEAMS_ASSIGNED,
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
466
             action: actions.CONTACT_TEAMS},
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
467
            {singular: reasons.ADMIN_TEAM_ASSIGNED,
468
             plural: reasons.ADMIN_TEAMS_ASSIGNED,
469
             action: actions.CHANGE_ASSIGNEES}));
12556.6.7 by Danilo Segan
Start gathering subscription info.
470
}
12556.6.26 by Danilo Segan
Lint fixes.
471
namespace._gather_subscriptions_as_assignee =
472
        gather_subscriptions_as_assignee;
12556.6.7 by Danilo Segan
Start gathering subscription info.
473
474
/**
12850.1.1 by Danilo Segan
Add a test for grouping of pillars.
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) {
12850.1.3 by Danilo Segan
Fix a lint problem introduced with this branch.
479
    var index, sub;
480
    for (index = 0; index < subscriptions.length; index++) {
481
        sub = subscriptions[index];
12850.1.1 by Danilo Segan
Add a test for grouping of pillars.
482
        if (sub.reason === subscription.reason &&
483
            sub.action === subscription.action) {
484
            var are_vars_same = true;
12850.1.3 by Danilo Segan
Fix a lint problem introduced with this branch.
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
                    }
12850.1.1 by Danilo Segan
Add a test for grouping of pillars.
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
/**
12556.6.17 by Danilo Segan
Add supervisor handling.
509
 * Gather subscription information for implicit bug supervisor.
510
 */
511
function gather_subscriptions_as_supervisor(category) {
512
    var subscriptions = [];
513
    var reasons = namespace._reasons;
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
514
    var index, team_subscription, team_link;
12556.6.17 by Danilo Segan
Add supervisor handling.
515
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
516
    for (index = 0; index < category.personal.length; index++) {
12556.6.17 by Danilo Segan
Add supervisor handling.
517
        var subscription = category.personal[index];
12850.1.1 by Danilo Segan
Add a test for grouping of pillars.
518
        add_subscription_to_set(subscriptions, {
12556.6.17 by Danilo Segan
Add supervisor handling.
519
            reason: reasons.YOU_OWNER,
520
            vars: {
12556.6.18 by Danilo Segan
Add tests for supervisor.
521
                pillar: get_link_data(subscription.pillar)
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
522
            },
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
523
            action: actions.SET_BUG_SUPERVISOR,
524
            args: {pillar: subscription.pillar}
12556.6.17 by Danilo Segan
Add supervisor handling.
525
        });
526
    }
527
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
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);
12850.1.1 by Danilo Segan
Add a test for grouping of pillars.
531
        add_subscription_to_set(subscriptions, {
12556.6.17 by Danilo Segan
Add supervisor handling.
532
            reason: reasons.TEAM_OWNER,
533
            vars: {
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
534
                team: team_link,
12556.6.18 by Danilo Segan
Add tests for supervisor.
535
                pillar: get_link_data(team_subscription.pillar)
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
536
            },
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
537
            action: actions.CONTACT_TEAMS,
538
            args: {teams: [team_link]}
12556.6.17 by Danilo Segan
Add supervisor handling.
539
        });
540
    }
541
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
542
    for (index = 0; index < category.as_team_admin.length; index++) {
543
        team_subscription = category.as_team_admin[index];
12850.1.1 by Danilo Segan
Add a test for grouping of pillars.
544
        add_subscription_to_set(subscriptions, {
12556.6.17 by Danilo Segan
Add supervisor handling.
545
            reason: reasons.ADMIN_TEAM_OWNER,
546
            vars: {
12556.6.18 by Danilo Segan
Add tests for supervisor.
547
                team: get_link_data(team_subscription.principal),
548
                pillar: get_link_data(team_subscription.pillar)
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
549
            },
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
550
            action: actions.SET_BUG_SUPERVISOR,
551
            args: {pillar: team_subscription.pillar}
12556.6.17 by Danilo Segan
Add supervisor handling.
552
        });
553
    }
554
555
    return subscriptions;
556
}
557
namespace._gather_subscriptions_as_supervisor =
558
        gather_subscriptions_as_supervisor;
559
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
560
function gather_dupe_subscriptions_by_team(team_subscriptions,
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
561
                                           singular, plural, action) {
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
562
    var subscriptions = [];
12830.1.15 by Danilo Segan
Move vars to the top of functions.
563
    var index;
564
    var subscription, sub;
565
    var added_bug;
566
    var team_dupes_idx, team_dupes;
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
567
568
    // Collated list of { team: ..., bugs: []} records.
569
    var dupes_by_teams = [];
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
570
    for (index = 0; index < team_subscriptions.length; index++) {
12830.1.15 by Danilo Segan
Move vars to the top of functions.
571
        subscription = team_subscriptions[index];
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
572
        // Find the existing team reference.
12830.1.15 by Danilo Segan
Move vars to the top of functions.
573
        added_bug = false;
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
574
        for (team_dupes_idx = 0; team_dupes_idx < dupes_by_teams.length;
575
             team_dupes_idx++) {
12830.1.15 by Danilo Segan
Move vars to the top of functions.
576
            team_dupes = dupes_by_teams[team_dupes_idx];
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
577
            if (team_dupes.team === subscription.principal) {
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
578
                team_dupes.bugs.push(get_bug_link_data(subscription.bug));
579
                added_bug = true;
580
                break;
581
            }
582
        }
583
        if (!added_bug) {
584
            dupes_by_teams.push({
585
                team: subscription.principal,
586
                bugs: [get_bug_link_data(subscription.bug)]
587
            });
588
        }
589
    }
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
590
    for (team_dupes_idx = 0; team_dupes_idx < dupes_by_teams.length;
591
         team_dupes_idx++) {
12830.1.15 by Danilo Segan
Move vars to the top of functions.
592
        team_dupes = dupes_by_teams[team_dupes_idx];
593
        sub = choose_by_number(
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
594
            team_dupes.bugs.length,
595
            { reason: singular,
596
              vars: { duplicate_bug: team_dupes.bugs[0],
597
                      team: get_link_data(team_dupes.team) }},
598
            { reason: plural,
599
              vars: { duplicate_bugs: team_dupes.bugs,
600
                      team: get_link_data(team_dupes.team) }});
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
601
        sub.action = action;
602
        sub.args = { teams: [sub.vars.team],
603
                     bugs: team_dupes.bugs };
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
604
        subscriptions.push(sub);
605
    }
606
    return subscriptions;
607
}
608
12556.6.17 by Danilo Segan
Add supervisor handling.
609
/**
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
610
 * Gather subscription information from duplicate bug subscriptions.
611
 */
612
function gather_subscriptions_from_duplicates(category) {
613
    var subscriptions = [];
614
    var reasons = namespace._reasons;
12830.1.15 by Danilo Segan
Move vars to the top of functions.
615
    var index, dupes, subscription;
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
616
617
    if (category.personal.length > 0) {
12830.1.15 by Danilo Segan
Move vars to the top of functions.
618
        dupes = [];
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
619
        for (index = 0; index < category.personal.length; index++) {
12830.1.15 by Danilo Segan
Move vars to the top of functions.
620
            subscription = category.personal[index];
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
621
            dupes.push(
622
                get_bug_link_data(subscription.bug));
623
        }
624
        var sub = choose_by_number(
625
            dupes.length,
626
            { reason: reasons.YOU_SUBSCRIBED_TO_DUPLICATE,
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
627
              vars: { duplicate_bug: dupes[0] }},
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
628
            { reason: reasons.YOU_SUBSCRIBED_TO_DUPLICATES,
12556.7.24 by Gary Poster
tests pass for incremental step. Submitting for review
629
              vars: { duplicate_bugs: dupes }});
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
630
        sub.action = actions.UNSUBSCRIBE_DUPLICATES;
631
        sub.args = { bugs: dupes };
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
632
        subscriptions.push(sub);
633
    }
634
635
    // Get subscriptions as team member, grouped by teams.
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
636
    subscriptions = subscriptions.concat(
637
        gather_dupe_subscriptions_by_team(
638
            category.as_team_member,
639
            reasons.TEAM_SUBSCRIBED_TO_DUPLICATE,
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
640
            reasons.TEAM_SUBSCRIBED_TO_DUPLICATES,
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
641
            actions.CONTACT_TEAMS));
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
642
643
    // Get subscriptions as team admin, grouped by teams.
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
644
    subscriptions = subscriptions.concat(
645
        gather_dupe_subscriptions_by_team(
646
            category.as_team_admin,
647
            reasons.ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATE,
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
648
            reasons.ADMIN_TEAM_SUBSCRIBED_TO_DUPLICATES,
649
            actions.UNSUBSCRIBE_DUPLICATES));
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
650
651
    return subscriptions;
652
}
653
namespace._gather_subscriptions_from_duplicates =
654
        gather_subscriptions_from_duplicates;
655
656
/**
12556.6.22 by Danilo Segan
Add support for direct team subscription.
657
 * Gather subscription information from direct team subscriptions.
658
 */
659
function gather_subscriptions_through_team(category) {
660
    var reasons = namespace._reasons;
12556.6.23 by Danilo Segan
Refactor to introduce gather_subscriptions_by_role common to team-subs and assignment.
661
    return gather_subscriptions_by_role(
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
662
        category,
663
        {singular: reasons.TEAM_SUBSCRIBED,
664
         plural: reasons.TEAMS_SUBSCRIBED,
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
665
         action: actions.CONTACT_TEAMS},
12556.7.23 by Gary Poster
commit sketch for Danilo to look at
666
        {singular: reasons.ADMIN_TEAM_SUBSCRIBED,
667
         plural:reasons.ADMIN_TEAMS_SUBSCRIBED,
668
         action: actions.CHANGE_TEAM_SUBSCRIPTIONS});
12556.6.22 by Danilo Segan
Add support for direct team subscription.
669
}
670
namespace._gather_subscriptions_through_team =
671
        gather_subscriptions_through_team;
12556.6.24 by Danilo Segan
Refactor dupe-subscriptio gathering to reuse some code.
672
12556.6.22 by Danilo Segan
Add support for direct team subscription.
673
/**
12556.6.7 by Danilo Segan
Start gathering subscription info.
674
 * Gather all non-direct subscriptions into a list.
675
 */
676
function gather_nondirect_subscriptions(info) {
677
    var subscriptions = [];
678
679
    return subscriptions
680
        .concat(gather_subscriptions_as_assignee(info.as_assignee))
12556.6.20 by Danilo Segan
Add support for duplicate subscriptions.
681
        .concat(gather_subscriptions_from_duplicates(info.from_duplicate))
12556.6.22 by Danilo Segan
Add support for direct team subscription.
682
        .concat(gather_subscriptions_through_team(info.direct))
12556.6.19 by Danilo Segan
Remove unused code.
683
        .concat(gather_subscriptions_as_supervisor(info.as_owner));
12556.6.7 by Danilo Segan
Start gathering subscription info.
684
685
}
12556.6.3 by Danilo Segan
Remove unneeded reasons, prefer direct subscription over the assignee.
686
12556.8.10 by Benji York
got all the scaffolding in place
687
// This mapping contains the IDs of elements that will be made visible if they
688
// apply to the current bug.
689
var action_ids = {
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
690
    mute: 'mute-direct-subscription',
691
    unmute: 'unmute-direct-subscription',
692
    subscribe_all: 'select-direct-subscription-discussion',
693
    subscribe_metadata: 'select-direct-subscription-metadata',
694
    subscribe_closed: 'select-direct-subscription-lifecycle',
695
    subscribe_only_metadata: 'select-only-direct-subscription-metadata',
696
    subscribe_only_closed: 'select-only-direct-subscription-lifecycle',
697
    unsubscribe: 'remove-direct-subscription',
698
    unsubscribe_with_warning: 'remove-direct-subscription-with-warning'
12556.8.15 by Gary Poster
include review patch
699
};
12556.11.8 by Gary Poster
add tests for reveal_direct_description_actions
700
namespace._action_ids = action_ids;
12556.8.10 by Benji York
got all the scaffolding in place
701
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
702
/**
703
 * Get direct subscription information.
704
 */
705
function get_direct_subscription_information(info) {
706
    var reason;
707
    var reasons = namespace._reasons;
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
708
    var reductions = [];
709
    var increases = [];
12915.1.1 by Gary Poster
when you only have structural subscriptions to a bug, the +subscriptions page should give the proper summary and options
710
    if (info.count === 0 && !has_structural_subscriptions()) {
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
711
        // The user has no subscriptions at all.
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
712
        reason = reasons.NOT_SUBSCRIBED;
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
713
        increases.push(action_ids.subscribe_all);
714
        increases.push(action_ids.subscribe_metadata);
715
        increases.push(action_ids.subscribe_closed);
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
716
    } else if (info.muted) {
12556.8.10 by Benji York
got all the scaffolding in place
717
        // The user has a muted direct subscription.
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
718
        reason = reasons.MUTED_SUBSCRIPTION;
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
719
        increases.push(action_ids.unmute);
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
720
    } else if (info.direct.personal.length > 0) {
12556.8.10 by Benji York
got all the scaffolding in place
721
        // The user has a direct personal subscription.
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
722
        if (info.direct.personal.length > 1) {
723
            Y.error(
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
724
                'Programmer error: a person should not have more than ' +
12556.7.22 by Danilo Segan
Fix indentation.
725
                'one direct personal subscription.');
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
726
        }
727
        var subscription = info.direct.personal[0];
728
        var bug = subscription.bug;
729
        if (subscription.principal_is_reporter) {
730
            reason = reasons.YOU_REPORTED;
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
731
        } else if (bug['private']) {
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
732
            reason = reasons.YOU_SUBSCRIBED_BUG_SUPERVISOR;
733
        } else if (bug.security_related) {
734
            // If bug is both private and security-related, you'll
735
            // only get the description talking about privacy.
736
            // Not considered a big deal.
737
            reason = reasons.YOU_SUBSCRIBED_SECURITY_CONTACT;
738
        } else {
739
            reason = reasons.YOU_SUBSCRIBED;
740
        }
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
741
        reductions.push(action_ids.mute);
742
        switch (subscription.subscription.bug_notification_level) {
743
            case 'Discussion':
744
                reductions.push(action_ids.subscribe_only_metadata);
745
                reductions.push(action_ids.subscribe_only_closed);
746
                break;
747
            case 'Details':
748
                increases.push(action_ids.subscribe_all);
749
                reductions.push(action_ids.subscribe_only_closed);
750
                break;
751
            case 'Lifecycle':
752
                increases.push(action_ids.subscribe_all);
753
                increases.push(action_ids.subscribe_metadata);
754
                break;
755
            default:
756
                Y.error('Programmer error: unknown bug notification level: '+
757
                        subscription.subscription.bug_notification_level);
758
        }
12556.8.10 by Benji York
got all the scaffolding in place
759
        if (info.count > 1) {
760
            // The user has a non-personal subscription as well as a direct
761
            // personal subscription.
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
762
            reductions.push(action_ids.unsubscribe_with_warning);
12556.8.10 by Benji York
got all the scaffolding in place
763
        } else {
764
            // The user just has the direct personal subscription.
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
765
            reductions.push(action_ids.unsubscribe);
12556.8.10 by Benji York
got all the scaffolding in place
766
        }
767
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
768
    } else {
12556.7.13 by Danilo Segan
Simplify direct subscription info message.
769
        // No direct subscriptions, but there are other
770
        // subscriptions (because info.count != 0).
771
        reason = reasons.NOT_PERSONALLY_SUBSCRIBED;
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
772
        reductions.push(action_ids.mute);
773
        reductions.push(action_ids.subscribe_only_metadata);
774
        reductions.push(action_ids.subscribe_only_closed);
7675.1160.7 by Gary Poster
use mute icons; fix subscription.js to handle new mute behavior along the way.
775
        increases.push(action_ids.subscribe_all);
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
776
    }
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
777
    return {reason: reason, reductions: reductions, increases: increases};
12556.7.1 by Danilo Segan
Provide get_direct_subscription_information method.
778
}
779
namespace._get_direct_subscription_information =
780
        get_direct_subscription_information;
781
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
782
/**
783
 * Returns an anchor element HTML for an ObjectLink element.
784
 * It safely encodes the `title` and `url` elements to avoid any XSS vectors.
785
 *
786
 * @method get_objectlink_html
787
 * @param {Object} element ObjectLink element or a simple string.
788
 * @returns {String} HTML for the A element representing passed in
789
 *     ObjectLink `element`.  If `element` is a string, return it unmodified.
790
 */
12556.7.9 by Danilo Segan
Improve safely_render_description.
791
function get_objectlink_html(element) {
12556.7.11 by Danilo Segan
Add tests for get_objectlink_html.
792
    if (Y.Lang.isString(element)) {
793
        return element;
794
    } else if (Y.Lang.isObject(element)) {
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
795
        if (element.url === undefined && element.title === undefined) {
12556.7.11 by Danilo Segan
Add tests for get_objectlink_html.
796
            Y.error('Not a proper ObjectLink.');
797
        }
798
        var node = Y.Node.create('<div></div>');
799
        node.appendChild(
800
            Y.Node.create('<a></a>')
801
                .set('href', element.url)
802
                .set('text', element.title));
803
        var text = node.get('innerHTML');
12556.7.18 by Danilo Segan
Add tests for show_subscription_description.
804
        node.destroy(true);
12556.7.11 by Danilo Segan
Add tests for get_objectlink_html.
805
        return text;
806
    }
12556.7.9 by Danilo Segan
Improve safely_render_description.
807
}
12556.7.11 by Danilo Segan
Add tests for get_objectlink_html.
808
namespace._get_objectlink_html = get_objectlink_html;
12556.7.9 by Danilo Segan
Improve safely_render_description.
809
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
810
/**
811
 * Array sort function for objects sorting them by their `title` property.
812
 */
12556.7.9 by Danilo Segan
Improve safely_render_description.
813
function sort_by_title(a, b) {
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
814
    return ((a.title === b.title) ? 0 :
12556.7.9 by Danilo Segan
Improve safely_render_description.
815
            ((a.title > b.title) ? 1 : -1));
816
}
817
818
/**
12556.7.12 by Danilo Segan
Add tests for safely_render_description.
819
 * Renders the description in a safe manner escaping HTML as appropriate.
12556.7.9 by Danilo Segan
Improve safely_render_description.
820
 *
821
 * @method safely_render_description
822
 * @param {Object} subscription Object containing the string `reason` and
823
 *            object `vars` containing variables to be replaced in `reason`.
824
 * @param {Object} additional_vars Objects containing additional, global
825
 *            variables to also be replaced if not overridden.
826
 * @returns {String} `reason` with all {var} occurrences replaced with
827
 *            appropriate subscription.vars[var] values.
828
 */
829
function safely_render_description(subscription, additional_vars) {
830
    function var_replacer(key, vars) {
12830.1.15 by Danilo Segan
Move vars to the top of functions.
831
        var index, final_element, text_elements;
12556.7.9 by Danilo Segan
Improve safely_render_description.
832
        if (vars !== undefined) {
833
            if (Y.Lang.isArray(vars)) {
834
                vars.sort(sort_by_title);
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
835
                // This can handle plural or singular.
12830.1.15 by Danilo Segan
Move vars to the top of functions.
836
                final_element = get_objectlink_html(vars.pop());
837
                text_elements = [];
838
                for (index in vars) {
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
839
                    if (vars.hasOwnProperty(index)) {
840
                        text_elements.push(get_objectlink_html(vars[index]));
841
                    }
842
                }
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
843
                if (text_elements.length > 0) {
844
                    return text_elements.join(', ') + ' and ' + final_element;
845
                } else {
846
                    return final_element;
847
                }
12556.7.9 by Danilo Segan
Improve safely_render_description.
848
            } else {
849
                return get_objectlink_html(vars);
850
            }
851
        } else {
852
            if (Y.Lang.isObject(additional_vars) &&
853
                additional_vars.hasOwnProperty(key)) {
12556.7.12 by Danilo Segan
Add tests for safely_render_description.
854
                return get_objectlink_html(additional_vars[key]);
12556.7.9 by Danilo Segan
Improve safely_render_description.
855
            }
856
        }
12556.7.6 by Danilo Segan
Add prototype var substitution as well.
857
    }
12556.7.9 by Danilo Segan
Improve safely_render_description.
858
    return Y.substitute(subscription.reason, subscription.vars, var_replacer);
12556.7.6 by Danilo Segan
Add prototype var substitution as well.
859
}
12556.7.19 by Danilo Segan
Add a missing semi-colon.
860
namespace._safely_render_description = safely_render_description;
12556.7.6 by Danilo Segan
Add prototype var substitution as well.
861
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
862
/**
863
 * This is a simple helper function for the *_action functions below.  It
864
 * takes an id and returns a Y.Node div with that id.
865
 */
12556.8.10 by Benji York
got all the scaffolding in place
866
function make_action(id) {
867
    return Y.Node.create('<div/>')
868
        .addClass('hidden')
869
        .set('id', id);
870
}
871
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
872
/**
873
 * Return a node for muting the bug.
874
 */
12556.8.10 by Benji York
got all the scaffolding in place
875
function mute_action() {
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
876
    return make_action(action_ids.mute)
12556.11.1 by Gary Poster
initial cut of direct actions
877
        .append(
878
            make_action_link(
879
                'mute all emails from this bug',
7675.1160.7 by Gary Poster
use mute icons; fix subscription.js to handle new mute behavior along the way.
880
                 'mute', 'mute', {}));
12556.8.10 by Benji York
got all the scaffolding in place
881
}
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
882
namespace._mute_action = mute_action;
12556.8.10 by Benji York
got all the scaffolding in place
883
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
884
/**
885
 * Return a node for unmuting the bug.
886
 */
12556.8.10 by Benji York
got all the scaffolding in place
887
function unmute_action() {
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
888
    return make_action(action_ids.unmute)
12556.11.1 by Gary Poster
initial cut of direct actions
889
        .append(
890
            make_action_link(
7675.1160.7 by Gary Poster
use mute icons; fix subscription.js to handle new mute behavior along the way.
891
                'unmute emails from this bug',
892
                 'unmute', 'unmute', {}));
12556.8.10 by Benji York
got all the scaffolding in place
893
}
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
894
namespace._unmute_action = unmute_action;
12556.8.10 by Benji York
got all the scaffolding in place
895
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
896
/**
897
 * Return a node for subscribing to all emails from the bug.
898
 */
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
899
function subscribe_all_action() {
900
    return make_action(action_ids.subscribe_all)
12556.11.1 by Gary Poster
initial cut of direct actions
901
        .append(make_subscribe_link(
902
            'receive all emails about this bug', 'Discussion'));
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
903
}
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
904
namespace._subscribe_all_action = subscribe_all_action;
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
905
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
906
/**
907
 * Return a node for subscribing to emails from this bug other than comments.
908
 */
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
909
function subscribe_metadata_action() {
910
    return make_action(action_ids.subscribe_metadata)
12556.11.1 by Gary Poster
initial cut of direct actions
911
        .append(make_subscribe_link(
912
            'receive all emails about this bug except comments', 'Details'));
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
913
}
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
914
namespace._subscribe_metadata_action = subscribe_metadata_action;
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
915
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
916
/**
917
 * Return a node for subscribing to emails about this bug closing.
918
 */
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
919
function subscribe_closed_action() {
920
    return make_action(action_ids.subscribe_closed)
12556.11.1 by Gary Poster
initial cut of direct actions
921
        .append(make_subscribe_link(
922
            'only receive email when this bug is closed', 'Lifecycle'));
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
923
}
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
924
namespace._subscribe_closed_action = subscribe_closed_action;
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
925
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
926
/**
927
 * Return a node for reducing emails received from this bug to eliminate
928
 * comments.  This is functionally identical to subscribe_metadata_action,
929
 * but has different text and is presented as a reduction, not an increase.
930
 */
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
931
function subscribe_only_metadata_action() {
932
    return make_action(action_ids.subscribe_only_metadata)
12556.11.1 by Gary Poster
initial cut of direct actions
933
        .append(make_subscribe_link(
934
            'stop receiving comments from this bug', 'Details'));
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
935
}
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
936
namespace._subscribe_only_metadata_action = subscribe_only_metadata_action;
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
937
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
938
/**
939
 * Return a node for reducing emails received from this bug to eliminate
940
 * everything but closing notifications.  This is functionally identical to
941
 * subscribe_closed_action, but has different text and is presented as a
942
 * reduction, not an increase.
943
 */
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
944
function subscribe_only_closed_action() {
945
    return make_action(action_ids.subscribe_only_closed)
12556.11.1 by Gary Poster
initial cut of direct actions
946
        .append(make_subscribe_link(
947
            'only receive email when this bug is closed', 'Lifecycle'));
12556.8.10 by Benji York
got all the scaffolding in place
948
}
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
949
namespace._subscribe_only_closed_action = subscribe_only_closed_action;
12556.8.10 by Benji York
got all the scaffolding in place
950
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
951
/**
952
 * Return a node for unsubscribing to emails about this bug.
953
 */
12556.8.10 by Benji York
got all the scaffolding in place
954
function unsubscribe_action() {
12556.8.15 by Gary Poster
include review patch
955
    return make_action(action_ids.unsubscribe)
12556.11.1 by Gary Poster
initial cut of direct actions
956
        .append(
957
            make_action_link(
958
                'unsubscribe from this bug',
959
                 'remove', 'unsubscribe', {}));
12556.8.10 by Benji York
got all the scaffolding in place
960
}
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
961
namespace._unsubscribe_action = unsubscribe_action;
12556.8.10 by Benji York
got all the scaffolding in place
962
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
963
/**
964
 * Return a node for unsubscribing to emails about this bug.  This is
965
 * functionally identical to unsubscribe_action, but has different text and
966
 * includes a warning that unsubscribing may not stop all emails.  This node
967
 * is intended to be used if there are other, non-direct non-personal
968
 * subscriptions that will cause the person to receive emails.
969
 */
12556.8.10 by Benji York
got all the scaffolding in place
970
function unsubscribe_with_warning_action() {
12556.8.15 by Gary Poster
include review patch
971
    return make_action(action_ids.unsubscribe_with_warning)
12556.11.1 by Gary Poster
initial cut of direct actions
972
        .append(
12556.11.4 by Gary Poster
tweak presentation
973
            Y.Node.create('<span/>')
974
                .set('text', 'You can also '))
975
        .append(
976
            make_action_link(
977
                'unsubscribe from this bug',
978
                 'remove', 'unsubscribe', {}))
979
        .append(
980
            Y.Node.create('<span/>')
981
                .set('text',
982
                     '.  However, you also have other subscriptions to '+
983
                     'this bug that may send you email once you have '+
984
                     'unsubscribed.'));
12556.11.1 by Gary Poster
initial cut of direct actions
985
}
12556.11.9 by Gary Poster
add tests for all _action functions, and brief docstrings
986
namespace._unsubscribe_with_warning_action = unsubscribe_with_warning_action;
12556.11.1 by Gary Poster
initial cut of direct actions
987
12556.11.2 by Gary Poster
merge in changes from parent branch
988
/**
989
 * Makes links for subscribing actions.
990
 */
12556.11.1 by Gary Poster
initial cut of direct actions
991
function make_subscribe_link(text, level) {
992
    return make_action_link(
993
        text,
994
        'edit',
995
        'subscribe',
996
        {person: LP.links.me, level: level}
997
    );
998
}
999
13890.2.5 by Steve Kowalik
Correct XXX, and remove two fallacies.
1000
function make_action_link_function (
1001
    node, sprite_class, method_name, handler, parameters, client) {
13890.2.7 by Steve Kowalik
Comment, whitespace fixes, confirm LP.cache.bug_is_private is defined,
1002
    // Performs the heavy lifting for make_action_link.
13890.2.1 by Steve Kowalik
Dirty, dirty first shot attempt.
1003
    var config = {
1004
        on: {success:
1005
            function (maybe_sub) {
1006
                node.replaceClass('spinner', sprite_class);
1007
                var info = LP.cache.bug_subscription_info;
1008
                var old = info.direct.personal[0];
1009
                if (Y.Lang.isValue(maybe_sub)) {
1010
                    // Set the subscription in info and in cache.
1011
                    var sub = maybe_sub.getAttrs();
1012
                    if (Y.Lang.isValue(old)) {
1013
                        info.direct.personal[0].subscription = sub;
1014
                    } else {
1015
                        // We don't have enough information to calculate
1016
                        // everything on the fly. Luckily, we don't need
1017
                        // most of it, and we think it is alright to not
13890.2.5 by Steve Kowalik
Correct XXX, and remove two fallacies.
1018
                        // include the extra information about
13890.2.1 by Steve Kowalik
Dirty, dirty first shot attempt.
1019
                        // principal_is_reporter, security_contact_pillars,
1020
                        // and bug_supervisor_pillars.
1021
                        info.direct.personal.push(
1022
                            {principal: {},
1023
                            bug: {},
1024
                            subscription: sub,
1025
                            principal_is_reporter: false,
1026
                            security_contact_pillars: [],
1027
                            bug_supervisor_pillars: []
1028
                           });
1029
                       info.direct.count += 1;
1030
                       info.count += 1;
1031
                   }
1032
               } else {
1033
                   if (Y.Lang.isValue(old)) {
1034
                       info.direct.personal.pop();
1035
                       info.direct.count -= 1;
1036
                       info.count -= 1;
1037
                   }
1038
               }
1039
               if (method_name === 'mute') {
1040
                   info.muted = true;
1041
               } else if (method_name === 'unmute') {
1042
                   info.muted = false;
1043
               }
1044
               reveal_direct_description_actions(
1045
                   Y.one('#direct-subscription'),
1046
                   get_direct_subscription_information(info));
1047
            },
1048
        failure: handler.getFailureHandler()},
1049
        parameters: parameters
1050
    };
1051
    node.replaceClass(sprite_class, 'spinner');
13890.2.7 by Steve Kowalik
Comment, whitespace fixes, confirm LP.cache.bug_is_private is defined,
1052
    client.named_post(LP.cache.context.bug_link, method_name, config);
13890.2.1 by Steve Kowalik
Dirty, dirty first shot attempt.
1053
}
1054
12556.11.2 by Gary Poster
merge in changes from parent branch
1055
/**
1056
 * Makes links for all kinds of actions.
12883.1.2 by Brad Crittenden
Export _make_action_link for testing and add a few tests. More needed.
1057
 *
12883.1.7 by Brad Crittenden
Add test for make_action_link using [subscribe,mute,unsubscribe,unmute]
1058
 * The link will be constructed to have an icon followed by text with
1059
 * it all part of the <a> link.
12883.1.2 by Brad Crittenden
Export _make_action_link for testing and add a few tests. More needed.
1060
 *
12556.11.15 by Gary Poster
respond to review
1061
 * @param {String} text Text of the link to be created.
1062
 * @param {String} sprite_class Name of the sprite to use for an icon.
1063
 * @param {String} method_name API method to call on the bug_link when the
12883.1.7 by Brad Crittenden
Add test for make_action_link using [subscribe,mute,unsubscribe,unmute]
1064
 *            link is clicked.
12883.1.2 by Brad Crittenden
Export _make_action_link for testing and add a few tests. More needed.
1065
 * @param {Object} parameters Dict of parameters to be passed to method_name.
12556.11.2 by Gary Poster
merge in changes from parent branch
1066
 */
12556.11.1 by Gary Poster
initial cut of direct actions
1067
function make_action_link(text, sprite_class, method_name, parameters) {
1068
    var node = Y.Node.create('<a/>')
12556.11.15 by Gary Poster
respond to review
1069
        .set('text', text)
1070
        .set('href', '#') // Makes the mouse arrow change into a hand.
1071
        .addClass('sprite')
1072
        .addClass('modify')
1073
        .addClass(sprite_class)
1074
        .addClass('js-action');
12556.11.1 by Gary Poster
initial cut of direct actions
1075
    var client = lp_client();
1076
    var handler = new Y.lp.client.ErrorHandler();
1077
1078
    handler.showError = function(error_msg) {
1079
        Y.lp.app.errors.display_error(node, error_msg);
1080
    };
1081
    handler.clearProgressUI = function () {
1082
        node.replaceClass('spinner', sprite_class);
1083
    };
1084
    node.on(
1085
        'click',
1086
        function (e) {
1087
            e.halt();
13890.2.2 by Steve Kowalik
Correct usage of ConfirmationOverlay
1088
            var method_call = function() {
13890.2.5 by Steve Kowalik
Correct XXX, and remove two fallacies.
1089
                make_action_link_function(
1090
                    node, sprite_class, method_name, handler, parameters,
1091
                    client);
1092
            };
13890.2.1 by Steve Kowalik
Dirty, dirty first shot attempt.
1093
            // If this a private bug, and the user is unsubscribing
1094
            // themselves, give them a warning.
13890.2.7 by Steve Kowalik
Comment, whitespace fixes, confirm LP.cache.bug_is_private is defined,
1095
            if (
1096
                method_name === 'unsubscribe' &&
1097
                Y.Lang.isBoolean(LP.cache.bug_is_private) &&
1098
                LP.cache.bug_is_private) {
13890.2.5 by Steve Kowalik
Correct XXX, and remove two fallacies.
1099
                var content = [
1100
                    '<p>You will not have access to this bug or any of ',
1101
                    'its pages if you unsubscribe. If you want to stop ',
1102
                    'emails, choose the "Mute bug mail" option.</p><p>Do ',
1103
                    'you really want to unsubscribe from this bug?</p>'
1104
                    ].join('');
1105
                var ns = Y.lp.app.confirmationoverlay;
1106
                var co = new ns.ConfirmationOverlay({
13890.2.2 by Steve Kowalik
Correct usage of ConfirmationOverlay
1107
                    submit_fn: method_call,
13890.2.5 by Steve Kowalik
Correct XXX, and remove two fallacies.
1108
                    form_content: content,
1109
                    headerContent: 'Unsubscribe from this bug'
13890.2.1 by Steve Kowalik
Dirty, dirty first shot attempt.
1110
                });
13890.2.5 by Steve Kowalik
Correct XXX, and remove two fallacies.
1111
                co.show();
13890.2.1 by Steve Kowalik
Dirty, dirty first shot attempt.
1112
            } else {
13890.2.2 by Steve Kowalik
Correct usage of ConfirmationOverlay
1113
                method_call();
13890.2.1 by Steve Kowalik
Dirty, dirty first shot attempt.
1114
            }
1115
        });
12556.11.1 by Gary Poster
initial cut of direct actions
1116
    return node;
12556.11.3 by Gary Poster
redraw personal subscriptions after change
1117
}
12883.1.2 by Brad Crittenden
Export _make_action_link for testing and add a few tests. More needed.
1118
namespace._make_action_link = make_action_link;
12556.8.15 by Gary Poster
include review patch
1119
1120
function border_box(title, content_div) {
1121
    return Y.Node.create('<div/>')
1122
        .addClass('hidden')
1123
        .setStyle('border', '1px solid #ddd')
1124
        .setStyle('padding', '0 1em 1em 1em')
12872.3.3 by Brad Crittenden
Update styles to make prettier
1125
        .setStyle('marginTop', '1em')
12556.8.15 by Gary Poster
include review patch
1126
        .append(Y.Node.create('<span/>')
1127
            .setStyle('backgroundColor', '#fff')
1128
            .setStyle('float', 'left')
12872.3.3 by Brad Crittenden
Update styles to make prettier
1129
            .setStyle('marginTop', '-0.8em')
1130
            .setStyle('padding', '0 0.5em')
12556.8.15 by Gary Poster
include review patch
1131
            .set('text', title))
1132
        .append(content_div
1133
            .setStyle('clear', 'both')
1134
            .setStyle('padding', '1em 0 0 1em'));
12556.8.10 by Benji York
got all the scaffolding in place
1135
}
1136
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1137
/**
1138
 * Creates a node to store the direct subscription information.
1139
 *
12556.11.8 by Gary Poster
add tests for reveal_direct_description_actions
1140
 * @returns {Object} Y.Node with the ID of 'direct-description' with the
1141
 *     expected structure for reason, reducing actions and increasing actions.
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1142
 */
12556.11.8 by Gary Poster
add tests for reveal_direct_description_actions
1143
function get_direct_description_node() {
12556.8.15 by Gary Poster
include review patch
1144
    var less_email_title = 'If you don\'t want to receive emails about '+
1145
        'this bug you can';
1146
    var more_email_title = 'If you want to receive more emails about '+
1147
        'this bug you can';
12556.8.10 by Benji York
got all the scaffolding in place
1148
    var direct_node = Y.Node.create('<div/>')
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1149
        .set('id', 'direct-subscription')
12556.11.15 by Gary Poster
respond to review
1150
        // Make element for reason's text.
12872.3.3 by Brad Crittenden
Update styles to make prettier
1151
        .append(Y.Node.create('<div/>')
12872.3.4 by Brad Crittenden
Merged Gary's changes with conflict resolution
1152
            .setStyle('paddingBottom', '1.0em')
1153
            .addClass('reason'))
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
1154
        // Make border box for actions that reduce email.
12556.8.15 by Gary Poster
include review patch
1155
        .append(border_box(less_email_title, Y.Node.create('<div/>')
12556.8.10 by Benji York
got all the scaffolding in place
1156
                .append(mute_action())
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
1157
                .append(subscribe_only_metadata_action())
1158
                .append(subscribe_only_closed_action())
12556.8.15 by Gary Poster
include review patch
1159
                .append(unsubscribe_action()))
1160
            .addClass('reductions'))
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
1161
        // Make border box for actions that increase email.
12556.8.15 by Gary Poster
include review patch
1162
        .append(border_box(more_email_title, Y.Node.create('<div/>')
12556.8.10 by Benji York
got all the scaffolding in place
1163
                .append(unmute_action())
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
1164
                .append(subscribe_all_action())
1165
                .append(subscribe_metadata_action())
12556.8.15 by Gary Poster
include review patch
1166
                .append(subscribe_closed_action()))
1167
            .addClass('increases'))
12556.8.10 by Benji York
got all the scaffolding in place
1168
        // Add the unsubscribe action for when they have other subscriptions.
1169
        .append(unsubscribe_with_warning_action());
12556.11.3 by Gary Poster
redraw personal subscriptions after change
1170
    return direct_node;
1171
}
1172
namespace._get_direct_description_node = get_direct_description_node;
12556.8.10 by Benji York
got all the scaffolding in place
1173
12556.11.8 by Gary Poster
add tests for reveal_direct_description_actions
1174
/**
1175
 * Mutates direct subscription node to render the appropriate information.
1176
 *
1177
 * @param {Object} direct_node The Y.Node as generated by
1178
 *     get_direct_description_node.
1179
 * @param {Object} direct_info An object as returned from
1180
 *     get_direct_subscription_information.  Should have a text `reason`,
1181
 *     a `reductions` list of reduction action ids to reveal, and a
1182
 *     `increases` list of increasing action ids to reveal.
1183
 */
1184
function reveal_direct_description_actions(direct_node, direct_info) {
12556.11.3 by Gary Poster
redraw personal subscriptions after change
1185
    var i;
1186
    var key;
12556.11.4 by Gary Poster
tweak presentation
1187
    direct_node.one('.reason').set('text', direct_info.reason);
1188
    // Hide all actions.  This is particularly important when we redraw
1189
    // actions after a successful direct subscription action.  After we hide
1190
    // these, we will reveal the ones we want, immediately below.
12556.11.3 by Gary Poster
redraw personal subscriptions after change
1191
    for (key in action_ids) {
12883.1.7 by Brad Crittenden
Add test for make_action_link using [subscribe,mute,unsubscribe,unmute]
1192
        if (action_ids.hasOwnProperty(key)) {
1193
            direct_node.one('#'+action_ids[key]).addClass('hidden');
1194
        }
12556.11.3 by Gary Poster
redraw personal subscriptions after change
1195
    }
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
1196
    if (direct_info.reductions.length !== 0) {
1197
        // If there are any actions the user can take, unhide the actions box
1198
        // and then unhide the specific actions that they should see.
1199
        direct_node.one('.reductions').removeClass('hidden');
1200
        for (i=0; i<direct_info.reductions.length; i++) {
1201
            direct_node.one('#'+direct_info.reductions[i])
1202
                .removeClass('hidden');
1203
        }
12556.11.3 by Gary Poster
redraw personal subscriptions after change
1204
    } else {
1205
        direct_node.one('.reductions').addClass('hidden');
12556.8.13 by Gary Poster
change approach for muted, unsubscribed, and subscribed direct personal subscriptions
1206
    }
1207
1208
    if (direct_info.increases.length !== 0) {
1209
        // If there are any actions the user can take, unhide the actions box
1210
        // and then unhide the specific actions that they should see.
1211
        direct_node.one('.increases').removeClass('hidden');
1212
        for (i=0; i<direct_info.increases.length; i++) {
1213
            direct_node.one('#'+direct_info.increases[i])
1214
                .removeClass('hidden');
12556.8.10 by Benji York
got all the scaffolding in place
1215
        }
12556.11.3 by Gary Poster
redraw personal subscriptions after change
1216
    } else {
1217
        direct_node.one('.increases').addClass('hidden');
12556.8.10 by Benji York
got all the scaffolding in place
1218
    }
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1219
}
12556.11.3 by Gary Poster
redraw personal subscriptions after change
1220
namespace._reveal_direct_description_actions =
1221
    reveal_direct_description_actions;
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1222
1223
/**
1224
 * Creates a node to store single subscription description.
1225
 *
1226
 * @param {Object} subscription Object containing `reason` and `vars`
1227
 *     to be substituted into `reason` with safely_render_description.
1228
 * @param {Object} extra_data Extra variables to substitute.
1229
 * @returns {Object} Y.Node with the class 'bug-subscription-description'
1230
 *     and textual description in a separate node with
1231
 *     class 'description-text'.
1232
 */
1233
function get_single_description_node(subscription, extra_data) {
13114.1.1 by Brad Crittenden
Use display:table-cell formatting to ensure proper spacing.
1234
    var node = Y.Node.create('<div />')
1235
        .setStyle('display', 'table')
1236
        .setStyle('width', '100%')
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1237
        .addClass('subscription-description');
1238
    node.appendChild(
13114.1.1 by Brad Crittenden
Use display:table-cell formatting to ensure proper spacing.
1239
        Y.Node.create('<div />')
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1240
            .addClass('description-text')
13114.1.1 by Brad Crittenden
Use display:table-cell formatting to ensure proper spacing.
1241
            .setStyle('display', 'table-cell')
1242
            .setStyle('width', '60%')
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1243
            .set('innerHTML',
1244
                 safely_render_description(subscription, extra_data)));
13114.1.1 by Brad Crittenden
Use display:table-cell formatting to ensure proper spacing.
1245
    var action_node = subscription.action(subscription.args);
1246
    if (Y.Lang.isValue(action_node)) {
1247
        var div = Y.Node.create('<div />');
1248
        div.appendChild(action_node);
1249
        div.setStyle('display', 'table-cell')
1250
           .setStyle('paddingLeft', '5em')
1251
           .setStyle('text-align', 'right');
1252
        node.appendChild(div);
1253
    }
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1254
    return node;
1255
}
1256
namespace._get_single_description_node = get_single_description_node;
1257
1258
/**
1259
 * Creates a node to store "other" subscriptions information.
1260
 * "Other" means any bug subscriptions which are not personal and direct.
1261
 *
1262
 * @param {Object} info LP.cache.bug_subscription_info object.
1263
 * @param {Object} extra_data Additional global variables to substitute
1264
 *     in strings.  Passed directly through to safely_render_description().
1265
 * @returns {Object} Y.Node with the ID of 'other-subscriptions' and
1266
 *     add descriptions of each subscription as a separate node.
1267
 */
1268
function get_other_descriptions_node(info, extra_data) {
1269
    var subs = gather_nondirect_subscriptions(info);
12830.1.15 by Danilo Segan
Move vars to the top of functions.
1270
    var index;
12556.8.4 by Danilo Segan
Put structural subscriptions inside 'other subscriptions' slide-out.
1271
    if (subs.length > 0 || has_structural_subscriptions()) {
12556.8.1 by Danilo Segan
Sliding of subscription descriptions.
1272
        var node = Y.Node.create('<div></div>')
1273
            .set('id', 'other-subscriptions');
12556.8.4 by Danilo Segan
Put structural subscriptions inside 'other subscriptions' slide-out.
1274
        var header = Y.Node.create('<div></div>')
12556.8.1 by Danilo Segan
Sliding of subscription descriptions.
1275
            .set('id', 'other-subscriptions-header');
1276
        var header_link = Y.Node.create('<a></a>')
1277
            .set('href', '#')
1278
            .set('text', 'Other subscriptions');
12556.8.4 by Danilo Segan
Put structural subscriptions inside 'other subscriptions' slide-out.
1279
        header.appendChild(header_link);
1280
        node.appendChild(header);
1281
        var list = Y.Node.create('<div></div>')
12556.8.1 by Danilo Segan
Sliding of subscription descriptions.
1282
            .set('id', 'other-subscriptions-list');
12556.8.4 by Danilo Segan
Put structural subscriptions inside 'other subscriptions' slide-out.
1283
        node.appendChild(list);
12556.8.1 by Danilo Segan
Sliding of subscription descriptions.
1284
12556.8.4 by Danilo Segan
Put structural subscriptions inside 'other subscriptions' slide-out.
1285
        setup_slider(list, header_link);
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1286
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
1287
        for (index = 0; index < subs.length; index++) {
12556.8.4 by Danilo Segan
Put structural subscriptions inside 'other subscriptions' slide-out.
1288
            list.appendChild(
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1289
                get_single_description_node(subs[index], extra_data));
1290
        }
1291
12556.8.1 by Danilo Segan
Sliding of subscription descriptions.
1292
        return node;
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1293
    } else {
1294
        return undefined;
1295
    }
1296
}
1297
namespace._get_other_descriptions_node = get_other_descriptions_node;
1298
12556.8.7 by Danilo Segan
Add tests for the new functionality.
1299
/**
1300
 * Are there any structural subscriptions that need to be rendered.
1301
 */
12556.8.4 by Danilo Segan
Put structural subscriptions inside 'other subscriptions' slide-out.
1302
function has_structural_subscriptions() {
1303
    return (LP.cache.subscription_info &&
1304
            LP.cache.subscription_info.length > 0);
1305
}
1306
12556.8.7 by Danilo Segan
Add tests for the new functionality.
1307
/**
1308
 * Sets up a slider that slides the `body` in and out when `header`
1309
 * is clicked.
1310
 */
12556.8.1 by Danilo Segan
Sliding of subscription descriptions.
1311
function setup_slider(body, header) {
1312
    // Hide the widget body contents.
1313
    body.addClass('lazr-closed');
1314
    body.setStyle('display', 'none');
1315
1316
    // Ensure that the widget header uses the correct sprite icon
1317
    // and gets the styling for javascript actions applied.
1318
    header.addClass('sprite');
1319
    header.addClass('treeCollapsed');
1320
    header.addClass('js-action');
1321
1322
    var slide;
1323
    function toggle_body_visibility(e) {
12556.8.4 by Danilo Segan
Put structural subscriptions inside 'other subscriptions' slide-out.
1324
        e.halt();
12556.8.1 by Danilo Segan
Sliding of subscription descriptions.
1325
        if (!slide) {
1326
            slide = Y.lazr.effects.slide_out(body);
1327
            header.replaceClass('treeCollapsed', 'treeExpanded');
1328
        } else {
1329
            slide.set('reverse', !slide.get('reverse'));
1330
            header.toggleClass('treeExpanded');
1331
            header.toggleClass('treeCollapsed');
1332
        }
1333
        slide.stop();
1334
        slide.run();
1335
    }
1336
    header.on('click', toggle_body_visibility);
1337
}
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1338
12556.7.18 by Danilo Segan
Add tests for show_subscription_description.
1339
/**
1340
 * Add descriptions for all non-structural subscriptions to the page.
1341
 *
1342
 * @param {Object} config Object specifying the node to populate in
1343
 *     `description_box` and allowing LP.cache.bug_subscription_info
1344
 *     override with `subscription_info` property.
1345
 */
12556.7.4 by Danilo Segan
Display the direct subscription reason on the page.
1346
function show_subscription_description(config) {
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
1347
    // Allow tests to pass subscription_info directly in.
1348
    var info = config.subscription_info || LP.cache.bug_subscription_info;
12556.7.4 by Danilo Segan
Display the direct subscription reason on the page.
1349
    // Replace subscription-cache-reference-* strings with actual
1350
    // object references.
1351
    replace_textual_references(info, LP.cache);
12556.7.17 by Danilo Segan
Expose bug_id as well.
1352
1353
    var extra_data = {
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
1354
        bug_id: '#' + info.bug_id.toString()
12556.7.17 by Danilo Segan
Expose bug_id as well.
1355
    };
12556.7.4 by Danilo Segan
Display the direct subscription reason on the page.
1356
1357
    var content_node = Y.one(config.description_box);
1358
12556.11.8 by Gary Poster
add tests for reveal_direct_description_actions
1359
    var direct_node = get_direct_description_node();
1360
    reveal_direct_description_actions(
1361
        direct_node,
1362
        get_direct_subscription_information(info));
12556.7.5 by Danilo Segan
Prototype rendering of all the subscription reasons.
1363
    content_node.appendChild(direct_node);
12556.7.18 by Danilo Segan
Add tests for show_subscription_description.
1364
12556.7.14 by Danilo Segan
Refactor node construction into smaller methods.
1365
    var other_node = get_other_descriptions_node(info, extra_data);
1366
    if (other_node !== undefined) {
12556.7.5 by Danilo Segan
Prototype rendering of all the subscription reasons.
1367
        content_node.appendChild(other_node);
12556.7.4 by Danilo Segan
Display the direct subscription reason on the page.
1368
    }
12556.6.2 by Danilo Segan
Implement assignee-reason getting and restructure reasons to be in a single object.
1369
}
12830.1.16 by Danilo Segan
Lint fixes for subscription.js.
1370
namespace.show_subscription_description = show_subscription_description;
12556.6.1 by Danilo Segan
Add the subscription info JS.
1371
1372
}, '0.1', {requires: [
12830.1.2 by Gary Poster
add sketches of a few real actions, hooked up.
1373
    'dom', 'event', 'node', 'substitute', 'lazr.effects', 'lp.app.errors',
13890.2.5 by Steve Kowalik
Correct XXX, and remove two fallacies.
1374
    'lp.app.confirmationoverlay', 'lp.client'
12556.6.1 by Danilo Segan
Add the subscription info JS.
1375
]});