~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/registry/javascript/structural-subscription.js

Show diffs side-by-side

added added

removed removed

Lines of Context:
311
311
    add_subscription_overlay.on('cancel', clean_up);
312
312
}
313
313
 
314
 
/*
315
 
 * Modify the DOM to insert a link or two into the global actions portlet.
316
 
 * If structural subscriptions already exist then a 'modify' link is
317
 
 * added.  Otherwise, just the 'add' link is put into the portlet.
318
 
 *
319
 
 * @method setup_subscription_links
320
 
 * @param {String} overlay_id Id of the overlay element.
321
 
 * @param {String} content_box_id Id of the element on the page where
322
 
 *     the overlay is anchored.
323
 
 */
324
 
function setup_subscription_links(overlay_id, content_box_id) {
325
 
    // Modify the menu-link-subscribe-to-bug-mail link to be visible.
326
 
    var link = Y.one('.menu-link-subscribe_to_bug_mail');
327
 
    if (!Y.Lang.isValue(link)) {
328
 
        Y.fail('"Subscribe to bug mail" link not found.');
329
 
    }
330
 
    link.removeClass('invisible-link');
331
 
    link.addClass('visible-link');
332
 
    link.on('click', function(e) {
333
 
        // Only proceed if the form content is already available.
334
 
        if (add_subscription_overlay) {
335
 
            e.halt();
336
 
            // We always set up the overlay as a blank canvas, in case it was
337
 
            // used before.
338
 
            clear_overlay(Y.one(content_box_id));
339
 
            add_subscription_overlay.show();
340
 
        }
341
 
    });
342
 
    link.addClass('js-action');
343
 
}                               // setup_subscription_links
344
 
 
345
314
/**
346
315
 * Reset the overlay form to initial values.
347
316
 */
348
 
function clear_overlay(content_node) {
349
 
    set_recipient(content_node, false, undefined);
 
317
function clear_overlay(content_node, no_recipient_picker) {
 
318
    if (no_recipient_picker) {
 
319
        set_recipient_label(content_node, undefined);
 
320
    } else {
 
321
        set_recipient(content_node, false, undefined);
 
322
    }
350
323
    content_node.one('[name="name"]').set('value', '');
351
324
    set_checkboxes(
352
325
        content_node, LP.cache.statuses, LP.cache.statuses);
733
706
        .appendChild(container)
734
707
            .one('#structural-subscription-context-title')
735
708
                .set('text', LP.cache.context.title);
 
709
    add_recipient_picker(content_node, hide_recipient_picker);
736
710
 
737
711
    var accordion = create_accordion('#accordion-overlay', content_node);
738
 
    add_recipient_picker(container, hide_recipient_picker);
739
712
 
740
713
    // Set up click handlers for the events radio buttons.
741
714
    var radio_group = Y.all('#events input');
770
743
    }
771
744
}
772
745
 
773
 
/*
 
746
/**
774
747
 * Create the LP client.
775
748
 *
776
749
 * @method setup_client
779
752
    namespace.lp_client = new Y.lp.client.Launchpad();
780
753
}                               // setup_client
781
754
 
782
 
/*
 
755
/**
783
756
 * External entry point for configuring the structual subscription.
784
757
 * @method setup_bug_subscriptions
785
758
 * @param {Object} config Object literal of config name/value pairs.
787
760
 *         the overlay will be anchored.
788
761
 */
789
762
namespace.setup_bug_subscriptions = function(config) {
790
 
    validate_config(config);
791
 
    Y.on('domready', function() {
792
 
        if (Y.Lang.isValue(config.lp_client)) {
793
 
            // Tests can specify an lp_client if they want to.
794
 
            namespace.lp_client = config.lp_client;
795
 
        } else {
796
 
            // Setup the Launchpad client.
797
 
            setup_client();
798
 
        }
 
763
    // Return if pre-setup fails.
 
764
    if (!pre_setup(config)) {
 
765
        return;
 
766
    }
799
767
 
800
 
        var overlay_id = setup_overlay(config.content_box, true);
801
 
        var submit_button = Y.Node.create(
802
 
            '<button type="submit" name="field.actions.create" ' +
803
 
            'value="Save Changes" class="lazr-pos lazr-btn" ' +
804
 
            '>OK</button>');
805
 
        // This is a bit of an odd approach, but it lets us retrofit code
806
 
        // without a large refactoring.  When edit_subscription_handler is
807
 
        // called, context.filter_info will have the information about the
808
 
        // filter that is being edited.
809
 
        var context = {};
810
 
        create_overlay(config.content_box, overlay_id, submit_button,
811
 
            function (form_data) {
812
 
                return edit_subscription_handler(context, form_data);});
813
 
        fill_in_bug_subscriptions(config, context);
814
 
        // We need to initialize the help links.  They may have already been
815
 
        // initialized except for the ones we added, so setupHelpTrigger
816
 
        // is idempotent.  Notice that this is old MochiKit code.
817
 
        forEach(findHelpLinks(), setupHelpTrigger);
818
 
    }, window);
 
768
    fill_in_bug_subscriptions(config);
819
769
};
820
770
 
821
 
/*
 
771
/**
822
772
 * Set up a validator function for a form input field.
823
773
 * @method add_input_validator
824
774
 * @param {Object} overlay Overlay object.
924
874
}
925
875
 
926
876
/**
 
877
 * Sets the recipient label according to the filter on the overlay.
 
878
 * Overlay must not have a recipient picker, but a simple recipient label.
 
879
 */
 
880
function set_recipient_label(content_node, filter_info) {
 
881
    var recipient_label = content_node.one('input[name="recipient"] + span'),
 
882
        teams = LP.cache.administratedTeams;
 
883
    if (filter_info !== undefined && filter_info.subscriber_is_team) {
 
884
        var i;
 
885
        for (i=0; i<teams.length; i++) {
 
886
            if (teams[i].link === filter_info.subscriber_link){
 
887
                recipient_label.set('text', teams[i].title);
 
888
                break;
 
889
            }
 
890
        }
 
891
    } else {
 
892
        recipient_label.set('text', 'Yourself');
 
893
    }
 
894
}
 
895
 
 
896
/**
 
897
 * Sets filter statuses and importances on the overlay based on the filter
 
898
 * data.
 
899
 */
 
900
function set_filter_statuses_and_importances(content_node, filter) {
 
901
    var is_lifecycle = filter.bug_notification_level==='Lifecycle',
 
902
        statuses = filter.statuses,
 
903
        importances = filter.importances;
 
904
    if (is_lifecycle) {
 
905
        statuses = LP.cache.statuses;
 
906
        importances = LP.cache.importances;
 
907
    } else {
 
908
        // An absence of values is equivalent to all values.
 
909
        if (statuses.length === 0) {
 
910
            statuses = LP.cache.statuses;
 
911
        }
 
912
        if (importances.length === 0) {
 
913
            importances = LP.cache.importances;
 
914
        }
 
915
    }
 
916
    set_checkboxes(content_node, LP.cache.statuses, statuses);
 
917
    set_checkboxes(
 
918
        content_node, LP.cache.importances, importances);
 
919
}
 
920
 
 
921
/**
 
922
 * Sets filter tags and tag matching options in the overlay based on the
 
923
 * filter data.
 
924
 */
 
925
function set_filter_tags(content_node, filter) {
 
926
    var is_lifecycle = filter.bug_notification_level==='Lifecycle';
 
927
    content_node.one('[name="tags"]').set(
 
928
        'value', is_lifecycle ? '' : filter.tags.join(' '));
 
929
    set_radio_buttons(
 
930
        content_node, [MATCH_ALL, MATCH_ANY],
 
931
        filter.find_all_tags ? MATCH_ALL : MATCH_ANY);
 
932
}
 
933
 
 
934
/**
 
935
 * Sets filter notification level radio/check boxes in the overlay
 
936
 * according to the filter data.
 
937
 */
 
938
function set_filter_notification_options(content_node, filter) {
 
939
    var is_lifecycle = filter.bug_notification_level==='Lifecycle',
 
940
        has_advanced_filters = !is_lifecycle && (
 
941
            filter.statuses.length ||
 
942
                filter.importances.length ||
 
943
                filter.tags.length) > 0,
 
944
        filters = has_advanced_filters ? [ADVANCED_FILTER] : [],
 
945
        event = ADDED_OR_CHANGED;
 
946
    // Chattiness: Lifecycle < Details < Discussion.
 
947
    switch (filter.bug_notification_level) {
 
948
        case 'Lifecycle':
 
949
            event = ADDED_OR_CLOSED;
 
950
            filters = [];
 
951
            break;
 
952
        case 'Details':
 
953
            filters.push(FILTER_COMMENTS);
 
954
            break;
 
955
    }
 
956
    // 'Discussion' case is the default and handled by the declared
 
957
    // values in the code.
 
958
    set_radio_buttons(
 
959
        content_node, [ADDED_OR_CLOSED, ADDED_OR_CHANGED], event);
 
960
    set_checkboxes(
 
961
        content_node, [FILTER_COMMENTS, ADVANCED_FILTER], filters);
 
962
    handle_change(ADDED_OR_CHANGED, FILTER_WRAPPER, {duration: 0});
 
963
    handle_change(ADVANCED_FILTER, ACCORDION_WRAPPER, {duration: 0});
 
964
}
 
965
 
 
966
/**
 
967
 * Loads all data from the filter into the overlay for editing.
 
968
 */
 
969
function load_overlay_with_filter_data(content_node, filter_info) {
 
970
    var filter = filter_info.filter;
 
971
    set_recipient_label(content_node, filter_info);
 
972
    content_node.one('[name="name"]').set('value',filter.description);
 
973
    set_filter_statuses_and_importances(content_node, filter);
 
974
    set_filter_tags(content_node, filter);
 
975
    set_filter_notification_options(content_node, filter);
 
976
}
 
977
 
 
978
/**
 
979
 * Show an overlay for editing a subscription.
 
980
 */
 
981
function show_edit_overlay(config, subscription, filter_info, filter_id) {
 
982
    Y.one(config.content_box).empty();
 
983
    var content_node = Y.one(config.content_box),
 
984
        overlay_id = setup_overlay(config.content_box, true),
 
985
        submit_button = Y.Node.create(
 
986
            '<button type="submit" name="field.actions.create" ' +
 
987
                'value="Save Changes" class="lazr-pos lazr-btn" ' +
 
988
                '>OK</button>');
 
989
 
 
990
    clear_overlay(content_node, true);
 
991
 
 
992
    var context = {
 
993
        filter_info: filter_info,
 
994
        filter_id: filter_id
 
995
    };
 
996
    create_overlay(
 
997
        config.content_box, overlay_id, submit_button,
 
998
        function (form_data) {
 
999
            return edit_subscription_handler(context, form_data);});
 
1000
 
 
1001
    load_overlay_with_filter_data(content_node, filter_info);
 
1002
    var title = subscription.target_title;
 
1003
    Y.one('#structural-subscription-context-title')
 
1004
        .set('text', title);
 
1005
    Y.one('#subscription-overlay-title')
 
1006
        .set('text', 'Edit subscription for '+title+' bugs');
 
1007
 
 
1008
    // We need to initialize the help links.  They may have already been
 
1009
    // initialized except for the ones we added, so setupHelpTrigger
 
1010
    // is idempotent.  Notice that this is old MochiKit code.
 
1011
    forEach(findHelpLinks(), setupHelpTrigger);
 
1012
    add_subscription_overlay.show();
 
1013
}
 
1014
 
 
1015
/**
927
1016
 * Return an edit handler for the specified filter.
928
1017
 */
929
 
function make_edit_handler(subscription, filter_info, filter_id,
930
 
                           config, context) {
 
1018
function make_edit_handler(subscription, filter_info, filter_id, config) {
931
1019
    // subscription is the filter's subscription.
932
1020
    // filter_info is the filter's information (from subscription.filters).
933
1021
    // filter_id is the numerical id for the filter, unique on the page.
934
1022
    // config is the configuration object used for the entire assembly of the
935
1023
    // page.
936
 
    // context is a way to communicate to the shared edit handler what filter
937
 
    // should be updated.
938
1024
    return function(e) {
939
 
        // Only proceed if the form content is already available.
940
 
        if (add_subscription_overlay) {
941
 
            e.halt();
942
 
            var content_node = Y.one(config.content_box),
943
 
                teams = LP.cache.administratedTeams,
944
 
                filter = filter_info.filter,
945
 
                is_lifecycle = filter.bug_notification_level==='Lifecycle',
946
 
                recipient_label = content_node.one(
947
 
                    'input[name="recipient"] + span'),
948
 
                statuses = filter.statuses,
949
 
                importances = filter.importances;
950
 
            if (filter_info.subscriber_is_team) {
951
 
                var i;
952
 
                for (i=0; i<teams.length; i++) {
953
 
                    if (teams[i].link === filter_info.subscriber_link){
954
 
                        recipient_label.set('text', teams[i].title);
955
 
                        break;
956
 
                    }
957
 
                }
958
 
            } else {
959
 
                recipient_label.set('text', 'Yourself');
960
 
            }
961
 
            content_node.one('[name="name"]').set('value',filter.description);
962
 
            if (is_lifecycle) {
963
 
                statuses = LP.cache.statuses;
964
 
                importances = LP.cache.importances;
965
 
            } else {
966
 
                // An absence of values is equivalent to all values.
967
 
                if (statuses.length === 0) {
968
 
                    statuses = LP.cache.statuses;
969
 
                }
970
 
                if (importances.length === 0) {
971
 
                    importances = LP.cache.importances;
972
 
                }
973
 
            }
974
 
            set_checkboxes(content_node, LP.cache.statuses, statuses);
975
 
            set_checkboxes(
976
 
                content_node, LP.cache.importances, importances);
977
 
            content_node.one('[name="tags"]').set(
978
 
                'value', is_lifecycle ? '' : filter.tags.join(' '));
979
 
            set_radio_buttons(
980
 
                content_node, [MATCH_ALL, MATCH_ANY],
981
 
                filter.find_all_tags ? MATCH_ALL : MATCH_ANY);
982
 
            var has_advanced_filters = !is_lifecycle && (
983
 
                    filter.statuses.length ||
984
 
                    filter.importances.length ||
985
 
                    filter.tags.length) > 0,
986
 
                filters = has_advanced_filters ? [ADVANCED_FILTER] : [],
987
 
                event = ADDED_OR_CHANGED;
988
 
            // Chattiness: Lifecycle < Details < Discussion.
989
 
            switch (filter.bug_notification_level) {
990
 
                case 'Lifecycle':
991
 
                    event = ADDED_OR_CLOSED;
992
 
                    filters = [];
993
 
                    break;
994
 
                case 'Details':
995
 
                    filters.push(FILTER_COMMENTS);
996
 
                    break;
997
 
                // case 'Discussion': This is already handled/the default.
998
 
                // default: If we get here then it is a programmer error.
999
 
            }
1000
 
            set_radio_buttons(
1001
 
                content_node, [ADDED_OR_CLOSED, ADDED_OR_CHANGED], event);
1002
 
            set_checkboxes(
1003
 
                content_node, [FILTER_COMMENTS, ADVANCED_FILTER], filters);
1004
 
            handle_change(ADDED_OR_CHANGED, FILTER_WRAPPER, {duration: 0});
1005
 
            handle_change(ADVANCED_FILTER, ACCORDION_WRAPPER, {duration: 0});
1006
 
            context.filter_info = filter_info;
1007
 
            context.filter_id = filter_id;
1008
 
            var title = subscription.target_title;
1009
 
            Y.one('#structural-subscription-context-title')
1010
 
                .set('text', title);
1011
 
            Y.one('#subscription-overlay-title')
1012
 
                .set('text', 'Edit subscription for '+title+' bugs');
1013
 
            add_subscription_overlay.show();
1014
 
        }
 
1025
        e.halt();
 
1026
        show_edit_overlay(config, subscription, filter_info, filter_id);
1015
1027
    };
1016
1028
}
1017
1029
 
1049
1061
/**
1050
1062
 * Attach activation (click) handlers to all of the edit links on the page.
1051
1063
 */
1052
 
function wire_up_edit_links(config, context) {
 
1064
function wire_up_edit_links(config) {
1053
1065
    var listing = Y.one('#subscription-listing');
1054
1066
    var subscription_info = LP.cache.subscription_info;
1055
1067
    var filter_id = 0;
1061
1073
            var filter_info = sub.filters[j];
1062
1074
            if (!filter_info.subscriber_is_team ||
1063
1075
                filter_info.user_is_team_admin) {
1064
 
                var node = Y.one('#subscription-filter-'+filter_id.toString());
 
1076
                var node = Y.one(
 
1077
                    '#subscription-filter-'+filter_id.toString());
1065
1078
                var edit_link = node.one('a.edit-subscription');
1066
1079
                var edit_handler = make_edit_handler(
1067
 
                    sub, filter_info, filter_id, config, context);
 
1080
                    sub, filter_info, filter_id, config);
1068
1081
                edit_link.on('click', edit_handler);
1069
1082
                var delete_link = node.one('a.delete-subscription');
1070
1083
                var delete_handler = make_delete_handler(
1079
1092
/**
1080
1093
 * Populate the subscription list DOM element with subscription descriptions.
1081
1094
 */
1082
 
function fill_in_bug_subscriptions(config, context) {
 
1095
function fill_in_bug_subscriptions(config) {
1083
1096
    validate_config(config);
1084
1097
 
1085
1098
    var listing = Y.one('#subscription-listing');
1159
1172
    }
1160
1173
    listing.appendChild(top_node);
1161
1174
 
1162
 
    wire_up_edit_links(config, context);
 
1175
    wire_up_edit_links(config);
1163
1176
}
1164
1177
 
1165
1178
/**
1267
1280
// Expose in the namespace for testing purposes.
1268
1281
namespace._validate_config = validate_config;
1269
1282
 
1270
 
/*
1271
 
 * External entry point for configuring the structual subscription.
1272
 
 * @method setup
1273
 
 * @param {Object} config Object literal of config name/value pairs.
1274
 
 *     config.content_box is the name of an element on the page where
1275
 
 *         the overlay will be anchored.
 
1283
/**
 
1284
 * Do pre-setup checks and initalizations.
 
1285
 * Sets up the LP client and ensures the user is logged-in.
1276
1286
 */
1277
 
namespace.setup = function(config) {
 
1287
function pre_setup(config) {
1278
1288
    validate_config(config);
1279
1289
 
1280
1290
    // If the user is not logged in, then we need to defer to the
1281
1291
    // default behaviour.
1282
1292
    if (LP.links.me === undefined) {
1283
 
        return;
 
1293
        return false;
1284
1294
    }
1285
1295
    if (Y.Lang.isValue(config.lp_client)) {
1286
1296
        // Tests can specify an lp_client if they want to.
1289
1299
        // Setup the Launchpad client.
1290
1300
        setup_client();
1291
1301
    }
1292
 
    // Create the overlay.
 
1302
    return true;
 
1303
}
 
1304
 
 
1305
/**
 
1306
 * Show the overlay for creating a new subscription.
 
1307
 */
 
1308
function show_add_overlay(config) {
 
1309
    Y.one(config.content_box).empty();
1293
1310
    var overlay_id = setup_overlay(config.content_box);
1294
 
    // Create the subscription links on the page.
1295
 
    setup_subscription_links(overlay_id, config.content_box);
 
1311
    clear_overlay(Y.one(config.content_box), false);
1296
1312
 
1297
1313
    var submit_button = Y.Node.create(
1298
1314
        '<button type="submit" name="field.actions.create" ' +
1299
1315
        'value="Create subscription" class="lazr-pos lazr-btn" '+
1300
1316
        '>OK</button>');
 
1317
 
1301
1318
    create_overlay(config.content_box, overlay_id, submit_button,
1302
1319
        save_subscription);
1303
1320
    // We need to initialize the help links.  They may have already been
1304
1321
    // initialized except for the ones we added, so setupHelpTrigger
1305
1322
    // is idempotent.  Notice that this is old MochiKit code.
1306
1323
    forEach(findHelpLinks(), setupHelpTrigger);
1307
 
};               // setup
 
1324
    add_subscription_overlay.show();
 
1325
    return overlay_id;
 
1326
}
 
1327
namespace._show_add_overlay = show_add_overlay;
 
1328
 
 
1329
/**
 
1330
 * Modify a link to pop up a subscription overlay.
 
1331
 *
 
1332
 * @method setup_subscription_link
 
1333
 * @param {String} link_id Id of the link element.
 
1334
 * @param {String} overlay_id Id of the overlay element.
 
1335
 */
 
1336
function setup_subscription_link(config, link_id) {
 
1337
    // Modify the menu-link-subscribe-to-bug-mail link to be visible.
 
1338
    var link = Y.one(link_id);
 
1339
    if (!Y.Lang.isValue(link)) {
 
1340
        Y.fail('Link to set as the pop-up link not found.');
 
1341
    }
 
1342
    link.removeClass('invisible-link');
 
1343
    link.addClass('visible-link');
 
1344
    link.on('click', function(e) {
 
1345
        e.halt();
 
1346
        show_add_overlay(config);
 
1347
    });
 
1348
    link.addClass('js-action');
 
1349
}                               // setup_subscription_links
 
1350
 
 
1351
/**
 
1352
 * External entry point for configuring the structural subscription.
 
1353
 * @method setup
 
1354
 * @param {Object} config Object literal of config name/value pairs.
 
1355
 *     config.content_box is the name of an element on the page where
 
1356
 *         the overlay will be anchored.
 
1357
 */
 
1358
namespace.setup = function(config) {
 
1359
    // Return if pre-setup fails.
 
1360
    if (!pre_setup(config)) {
 
1361
        return;
 
1362
    }
 
1363
 
 
1364
    // Create the subscription links on the page.
 
1365
    setup_subscription_link(config, '.menu-link-subscribe_to_bug_mail');
 
1366
}; // setup
1308
1367
 
1309
1368
}, '0.1', {requires: [
1310
1369
        'dom', 'node', 'lazr.anim', 'lazr.formoverlay', 'lazr.overlay',