]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1380437 - Implement REST API utility methods in global.js to replace bugzilla_aja...
authorKohei Yoshino <kohei.yoshino@gmail.com>
Wed, 3 Jul 2019 22:38:20 +0000 (18:38 -0400)
committerGitHub <noreply@github.com>
Wed, 3 Jul 2019 22:38:20 +0000 (18:38 -0400)
26 files changed:
extensions/Bitly/web/js/bitly.js
extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl
extensions/BugModal/web/bug_modal.css
extensions/BugModal/web/bug_modal.js
extensions/BugModal/web/comments.js
extensions/ComponentWatching/web/js/overlay.js
extensions/EditComments/web/js/inline-editor.js
extensions/EditComments/web/js/revisions.js
extensions/FlagTypeComment/web/js/ftc.js
extensions/GuidedBugEntry/web/js/guided.js
extensions/MyDashboard/lib/WebService.pm
extensions/MyDashboard/web/js/flags.js
extensions/MyDashboard/web/js/query.js
extensions/PhabBugz/web/js/phabricator.js
extensions/ProdCompSearch/web/js/prod_comp_search.js
extensions/REMO/web/js/payment.js
extensions/Review/web/js/review_history.js
js/account.js
js/bug.js
js/comment-tagging.js
js/field.js
js/global.js
js/instant-search.js
js/util.js
template/en/default/bug/show-header.html.tmpl
template/en/default/global/header.html.tmpl

index 1bc39d411a07b9de2b3c4dee7e3733ffc124a853..1de0d0421a8850a3cc4b6f07697777572d42473c 100644 (file)
@@ -9,7 +9,7 @@ $(function() {
     'use strict';
     var popup, urls = [];
 
-    function execute() {
+    async function execute() {
         var type = $('#bitly-type').val();
 
         if (urls[type]) {
@@ -18,17 +18,15 @@ $(function() {
         }
 
         $('#bitly-url').val('');
-        var request = `${BUGZILLA.config.basepath}rest/bitly/${type}?` +
-                      `url=${encodeURIComponent($('#bitly-shorten').data('url'))}&` +
-                      `Bugzilla_api_token=${encodeURIComponent(BUGZILLA.api_token)}`;
-        $.ajax(request)
-            .done(function(data) {
-                urls[type] = data.url;
-                $('#bitly-url').val(urls[type]).select().focus();
-            })
-            .fail(function(data) {
-                $('#bitly-url').val(data.responseJSON.message);
-            });
+
+        try {
+            const { url } = await Bugzilla.API.get(`bitly/${type}`, { url: $('#bitly-shorten').data('url') });
+
+            urls[type] = url;
+            $('#bitly-url').val(urls[type]).select().focus();
+        } catch ({ message }) {
+            $('#bitly-url').val(message);
+        }
     }
 
     $('#bitly-shorten')
index 058599c6810e9c91e6c8efab5a7c0f169522f7f8..609351dc812ef610bf4e9e9b8778e41e7a75d49c 100644 (file)
 
 [%# === header === %]
 
-<div role="status" id="xhr-error" style="display:none"></div>
+<div role="status" id="io-error" style="display:none"></div>
 <div role="status" id="floating-message" style="display:none">
   <div id="floating-message-text"></div>
 </div>
index 3fd0c4b98169bb8f1598885b1d46c98f37abd441..f4a9710bcc32faeef8e7be40b94d57dfeacba9f0 100644 (file)
@@ -970,7 +970,7 @@ h3.change-name a {
   vertical-align: top;
 }
 
-#xhr-error {
+#io-error {
   margin: 5px 0;
   border-radius: 2px;
   border: 1px solid var(--error-message-foreground-color);
index c8ca6fe59faa10815ba0b8969b371a293d39122e..c819d7aa26914437a06cf713ca64c6e8a8b60886 100644 (file)
@@ -266,50 +266,50 @@ $(function() {
         );
     }
 
-    function ccListUpdate() {
-        bugzilla_ajax(
-            {
-                url: `${BUGZILLA.config.basepath}rest/bug_modal/cc/${BUGZILLA.bug_id}`
-            },
-            function(data) {
-                $('#cc-list').html(data.html);
-                $('#cc-latch').data('fetched', true);
-                $('#cc-list .cc-user').hover(
-                    function() {
-                        $('#ccr-' + $(this).data('n')).css('visibility', 'visible');
-                    },
-                    function() {
-                        $('#ccr-' + $(this).data('n')).css('visibility', 'hidden');
+    async function ccListUpdate() {
+        try {
+            const { html } = await Bugzilla.API.get(`bug_modal/cc/${BUGZILLA.bug_id}`);
+
+            $('#io-error').empty().hide();
+            $('#cc-list').html(html);
+            $('#cc-latch').data('fetched', true);
+            $('#cc-list .cc-user').hover(
+                function() {
+                    $('#ccr-' + $(this).data('n')).css('visibility', 'visible');
+                },
+                function() {
+                    $('#ccr-' + $(this).data('n')).css('visibility', 'hidden');
+                }
+            );
+            $('#cc-list .show_usermenu').click(function() {
+                const $this = $(this);
+                return show_usermenu($this.data('user-id'), $this.data('user-email'), $this.data('show-edit'),
+                    $this.data('hide-profile'));
+            });
+            $('#cc-list .cc-remove')
+                .click(function(event) {
+                    event.preventDefault();
+                    $('#top-save-btn').show();
+                    var n = $(this).data('n');
+                    var ccu = $('#ccu-' + n);
+                    if (ccu.hasClass('cc-removed')) {
+                        ccu.removeClass('cc-removed');
+                        $('#cc-' + n).remove();
+                    }
+                    else {
+                        $('#removecc').val('on');
+                        ccu.addClass('cc-removed');
+                        $('<input>').attr({
+                            type: 'hidden',
+                            id: 'cc-' + n,
+                            value: $('#ccr-' + n).data('login'),
+                            name: 'cc'
+                        }).appendTo('#changeform');
                     }
-                );
-                $('#cc-list .show_usermenu').click(function() {
-                    const $this = $(this);
-                    return show_usermenu($this.data('user-id'), $this.data('user-email'), $this.data('show-edit'),
-                        $this.data('hide-profile'));
                 });
-                $('#cc-list .cc-remove')
-                    .click(function(event) {
-                        event.preventDefault();
-                        $('#top-save-btn').show();
-                        var n = $(this).data('n');
-                        var ccu = $('#ccu-' + n);
-                        if (ccu.hasClass('cc-removed')) {
-                            ccu.removeClass('cc-removed');
-                            $('#cc-' + n).remove();
-                        }
-                        else {
-                            $('#removecc').val('on');
-                            ccu.addClass('cc-removed');
-                            $('<input>').attr({
-                                type: 'hidden',
-                                id: 'cc-' + n,
-                                value: $('#ccr-' + n).data('login'),
-                                name: 'cc'
-                            }).appendTo('#changeform');
-                        }
-                    });
-            }
-        );
+        } catch ({ message }) {
+            $('#io-error').html(message).show('fast');
+        }
     }
 
     if (BUGZILLA.user.id) {
@@ -510,7 +510,7 @@ $(function() {
 
     // edit/save mode button
     $('#mode-btn')
-        .click(function(event) {
+        .click(async event => {
             event.preventDefault();
 
             // hide buttons, old error messages
@@ -535,119 +535,119 @@ $(function() {
             $('#mode-btn').prop('disabled', true);
 
             // load the missing select data
-            bugzilla_ajax(
-                {
-                    url: `${BUGZILLA.config.basepath}rest/bug_modal/edit/${BUGZILLA.bug_id}`
-                },
-                function(data) {
-                    $('#mode-btn').hide();
-
-                    // populate select menus
-                    Object.entries(data.options).forEach(([key, value]) => {
-                        const $select = document.querySelector(`#${key}`);
-                        if (!$select) return;
-                        // It can be radio-button-like UI
-                        const use_buttons = $select.matches('.buttons.toggle');
-                        const is_required = $select.matches('[aria-required="true"]');
-                        const selected = use_buttons ? $select.querySelector('input').value : $select.value;
-                        $select.innerHTML = '';
-                        value.forEach(({ name }) => {
-                            if (is_required && name === '--') {
-                                return;
-                            }
-                            if (use_buttons) {
-                                $select.insertAdjacentHTML('beforeend', `
-                                  <div class="item">
-                                    <input id="${$select.id}_${name}_radio" type="radio" name="${$select.id}"
-                                           value="${name}" ${name === selected ? 'checked' : ''}>
-                                    <label for="${$select.id}_${name}_radio">
-                                    ${$select.id === 'bug_type' ? `
-                                      <span class="bug-type-label iconic-text" data-type="${name}">
-                                        <span class="icon" aria-hidden="true"></span>${name}
-                                      </span>
-                                    ` : `${name}`}
-                                    </label>
-                                  </div>
-                                `);
-                            } else {
-                                $select.insertAdjacentHTML('beforeend', `
-                                  <option value="${name}" ${name === selected ? 'selected' : ''}>${name}</option>
-                                `);
-                            }
-                        });
-                        if ($select.matches('[multiple]') && value.length < 5) {
-                            $select.size = value.length;
-                        }
-                    });
+            try {
+                const data = await Bugzilla.API.get(`bug_modal/edit/${BUGZILLA.bug_id}`);
 
-                    // build our product description hash
-                    $.each(data.options.product, function() {
-                        products[this.name] = this.description;
-                    });
+                $('#io-error').empty().hide();
+                $('#mode-btn').hide();
 
-                    // keywords is a multi-value autocomplete
-                    keywords = data.keywords;
-                    $('#keywords')
-                        .devbridgeAutocomplete({
-                            appendTo: $('#main-inner'),
-                            forceFixPosition: true,
-                            lookup: function(query, done) {
-                                query = query.toLowerCase();
-                                var matchStart =
-                                    $.grep(keywords, function(keyword) {
-                                        return keyword.toLowerCase().substr(0, query.length) === query;
-                                    });
-                                var matchSub =
-                                    $.grep(keywords, function(keyword) {
-                                        return keyword.toLowerCase().indexOf(query) !== -1 &&
-                                            $.inArray(keyword, matchStart) === -1;
-                                    });
-                                var suggestions =
-                                    $.map($.merge(matchStart, matchSub), function(suggestion) {
-                                        return { value: suggestion };
-                                    });
-                                done({ suggestions: suggestions });
-                            },
-                            tabDisabled: true,
-                            delimiter: /,\s*/,
-                            minChars: 0,
-                            autoSelectFirst: false,
-                            triggerSelectOnValidInput: false,
-                            formatResult: function(suggestion, currentValue) {
-                                // disable <b> wrapping of matched substring
-                                return suggestion.value.htmlEncode();
-                            },
-                            onSearchStart: function(params) {
-                                var that = $(this);
-                                // adding spaces shouldn't initiate a new search
-                                var parts = that.val().split(/,\s*/);
-                                var query = parts[parts.length - 1];
-                                return query === $.trim(query);
-                            },
-                            onSelect: function() {
-                                this.value = this.value + ', ';
-                                this.focus();
-                            }
-                        })
-                        .addClass('bz_autocomplete');
-
-                    $('#cancel-btn').prop('disabled', false);
-                    $('#top-save-btn').show();
-                    $('#cancel-btn').show();
-                    $('#commit-btn').show();
-                },
-                function() {
-                    $('#mode-btn-readonly').show();
-                    $('#mode-btn-loading').hide();
-                    $('#mode-btn').prop('disabled', false);
-                    $('#mode-btn').show();
-                    $('#cancel-btn').hide();
-                    $('#commit-btn').hide();
-
-                    $('.edit-show').hide();
-                    $('.edit-hide').show();
+                // populate select menus
+                for (const [key, value] of Object.entries(data.options)) {
+                    const $select = document.querySelector(`#${key}`);
+                    if (!$select) {
+                        continue;
+                    }
+                    // It can be radio-button-like UI
+                    const use_buttons = $select.matches('.buttons.toggle');
+                    const is_required = $select.matches('[aria-required="true"]');
+                    const selected = use_buttons ? $select.querySelector('input').value : $select.value;
+                    $select.innerHTML = '';
+                    for (const { name } of value) {
+                        if (is_required && name === '--') {
+                            continue;
+                        }
+                        if (use_buttons) {
+                            $select.insertAdjacentHTML('beforeend', `
+                              <div class="item">
+                                <input id="${$select.id}_${name}_radio" type="radio" name="${$select.id}"
+                                        value="${name}" ${name === selected ? 'checked' : ''}>
+                                <label for="${$select.id}_${name}_radio">
+                                ${$select.id === 'bug_type' ? `
+                                  <span class="bug-type-label iconic-text" data-type="${name}">
+                                    <span class="icon" aria-hidden="true"></span>${name}
+                                  </span>
+                                ` : `${name}`}
+                                </label>
+                              </div>
+                            `);
+                        } else {
+                            $select.insertAdjacentHTML('beforeend', `
+                              <option value="${name}" ${name === selected ? 'selected' : ''}>${name}</option>
+                            `);
+                        }
+                    }
+                    if ($select.matches('[multiple]') && value.length < 5) {
+                        $select.size = value.length;
+                    }
                 }
-            );
+
+                // build our product description hash
+                for (const { name, description } of data.options.product) {
+                    products[name] = description;
+                }
+
+                // keywords is a multi-value autocomplete
+                keywords = data.keywords;
+                $('#keywords')
+                    .devbridgeAutocomplete({
+                        appendTo: $('#main-inner'),
+                        forceFixPosition: true,
+                        lookup: function(query, done) {
+                            query = query.toLowerCase();
+                            var matchStart =
+                                $.grep(keywords, function(keyword) {
+                                    return keyword.toLowerCase().substr(0, query.length) === query;
+                                });
+                            var matchSub =
+                                $.grep(keywords, function(keyword) {
+                                    return keyword.toLowerCase().indexOf(query) !== -1 &&
+                                        $.inArray(keyword, matchStart) === -1;
+                                });
+                            var suggestions =
+                                $.map($.merge(matchStart, matchSub), function(suggestion) {
+                                    return { value: suggestion };
+                                });
+                            done({ suggestions });
+                        },
+                        tabDisabled: true,
+                        delimiter: /,\s*/,
+                        minChars: 0,
+                        autoSelectFirst: false,
+                        triggerSelectOnValidInput: false,
+                        formatResult: function(suggestion, currentValue) {
+                            // disable <b> wrapping of matched substring
+                            return suggestion.value.htmlEncode();
+                        },
+                        onSearchStart: function(params) {
+                            var that = $(this);
+                            // adding spaces shouldn't initiate a new search
+                            var parts = that.val().split(/,\s*/);
+                            var query = parts[parts.length - 1];
+                            return query === $.trim(query);
+                        },
+                        onSelect: function() {
+                            this.value = this.value + ', ';
+                            this.focus();
+                        }
+                    })
+                    .addClass('bz_autocomplete');
+
+                $('#cancel-btn').prop('disabled', false);
+                $('#top-save-btn').show();
+                $('#cancel-btn').show();
+                $('#commit-btn').show();
+            } catch ({ message }) {
+                $('#io-error').html(message).show('fast');
+                $('#mode-btn-readonly').show();
+                $('#mode-btn-loading').hide();
+                $('#mode-btn').prop('disabled', false);
+                $('#mode-btn').show();
+                $('#cancel-btn').hide();
+                $('#commit-btn').hide();
+
+                $('.edit-show').hide();
+                $('.edit-hide').show();
+            }
         });
     $('#mode-btn').prop('disabled', false);
 
@@ -675,7 +675,7 @@ $(function() {
 
     // cc toggle (follow/stop following)
     $('#cc-btn')
-        .click(function(event) {
+        .click(async event => {
             event.preventDefault();
             var is_cced = $(event.target).data('is-cced') == '1';
 
@@ -731,36 +731,34 @@ $(function() {
                 $('#add-self-cc').attr('disabled', false);
             }
 
-            bugzilla_ajax(
-                {
-                    url: `${BUGZILLA.config.basepath}rest/bug/${BUGZILLA.bug_id}`,
-                    type: 'PUT',
-                    data: JSON.stringify({ cc: cc_change })
-                },
-                function(data) {
-                    $('#cc-btn').prop('disabled', false);
-                    if (!(data.bugs[0].changes && data.bugs[0].changes.cc))
-                        return;
-                    if (data.bugs[0].changes.cc.added == BUGZILLA.user.login) {
-                        $('#cc-btn')
-                            .text('Stop Following')
-                            .data('is-cced', '1');
-                    }
-                    else if (data.bugs[0].changes.cc.removed == BUGZILLA.user.login) {
-                        $('#cc-btn')
-                            .text('Follow')
-                            .data('is-cced', '0');
-                    }
-                    if ($('#cc-latch').data('expanded'))
-                        ccListUpdate();
-                },
-                function(message) {
-                    $('#cc-btn').prop('disabled', false);
-                    if ($('#cc-latch').data('expanded'))
-                        ccListUpdate();
+            try {
+                const { bugs } = await Bugzilla.API.put(`bug/${BUGZILLA.bug_id}`, { cc: cc_change });
+                const { changes } = bugs[0];
+
+                $('#io-error').empty().hide();
+                $('#cc-btn').prop('disabled', false);
+
+                if (!(changes && changes.cc)) {
+                    return;
                 }
-            );
 
+                if (changes.cc.added == BUGZILLA.user.login) {
+                    $('#cc-btn').text('Stop Following').data('is-cced', '1');
+                } else if (changes.cc.removed == BUGZILLA.user.login) {
+                    $('#cc-btn').text('Follow').data('is-cced', '0');
+                }
+
+                if ($('#cc-latch').data('expanded')) {
+                    ccListUpdate();
+                }
+            } catch ({ message }) {
+                $('#io-error').html(message).show('fast');
+                $('#cc-btn').prop('disabled', false);
+
+                if ($('#cc-latch').data('expanded')) {
+                    ccListUpdate();
+                }
+            }
         });
 
     // cancel button, reset the ui back to read-only state
@@ -903,18 +901,16 @@ $(function() {
             var prefix = "(In reply to " + comment_author + " from comment #" + comment_no + ")\n";
             var reply_text = "";
 
-            var quoteMarkdown = function($comment) {
+            var quoteMarkdown = async $comment => {
                 const uid = $comment.data('comment-id');
-                bugzilla_ajax(
-                    {
-                        url: `rest/bug/comment/${uid}`,
-                    },
-                    (data) => {
-                        const quoted = data['comments'][uid]['text'].replace(/\n/g, "\n> ");
-                        reply_text = `${prefix}> ${quoted}\n\n`;
-                        populateNewComment();
-                    }
-                );
+
+                try {
+                    const { comments } = await Bugzilla.API.get(`bug/comment/${uid}`, { include_fields: 'text' });
+                    const quoted = comments[uid]['text'].replace(/\n/g, '\n> ');
+
+                    reply_text = `${prefix}> ${quoted}\n\n`;
+                    populateNewComment();
+                } catch (ex) {}
             }
 
             var populateNewComment = function() {
@@ -1141,7 +1137,7 @@ $(function() {
         $(this).data('default', $(this).val());
     });
     $('#product')
-        .change(function(event) {
+        .change(async event => {
             $('#product-throbber').show();
             $('#component, #version, #target_milestone').attr('disabled', true);
 
@@ -1154,74 +1150,72 @@ $(function() {
                 }
             });
 
-            bugzilla_ajax(
-                {
-                    url: `${BUGZILLA.config.basepath}rest/bug_modal/new_product/${BUGZILLA.bug_id}?` +
-                         `product=${encodeURIComponent($('#product').val())}`
-                },
-                function(data) {
-                    $('#product-throbber').hide();
-                    $('#component, #version, #target_milestone').attr('disabled', false);
-                    var is_default = $('#product').val() == $('#product').data('default');
-
-                    // populate selects
-                    $.each(data, function(key, value) {
-                        if (key == 'groups') return;
-                        var el = $('#' + key);
-                        if (!el) return;
-                        el.empty();
-                        var selected = el.data('preselect');
-                        $(value).each(function(i, v) {
-                            el.append($('<option>', { value: v.name, text: v.name }));
-                            if (typeof selected === 'undefined' && v.selected)
-                                selected = v.name;
-                        });
-                        el.val(selected);
-                        el.prop('required', true);
-                        if (is_default) {
-                            el.removeClass('attention');
-                            el.val(el.data('default'));
-                        }
-                        else {
-                            el.addClass('attention');
-                        }
+            try {
+                const product = $('#product').val();
+                const data = await Bugzilla.API.get(`bug_modal/new_product/${BUGZILLA.bug_id}`, { product });
+
+                $('#io-error').empty().hide();
+                $('#product-throbber').hide();
+                $('#component, #version, #target_milestone').attr('disabled', false);
+                var is_default = $('#product').val() == $('#product').data('default');
+
+                // populate selects
+                $.each(data, function(key, value) {
+                    if (key == 'groups') return;
+                    var el = $('#' + key);
+                    if (!el) return;
+                    el.empty();
+                    var selected = el.data('preselect');
+                    $(value).each(function(i, v) {
+                        el.append($('<option>', { value: v.name, text: v.name }));
+                        if (typeof selected === 'undefined' && v.selected)
+                            selected = v.name;
                     });
+                    el.val(selected);
+                    el.prop('required', true);
+                    if (is_default) {
+                        el.removeClass('attention');
+                        el.val(el.data('default'));
+                    }
+                    else {
+                        el.addClass('attention');
+                    }
+                });
 
-                    // update groups
-                    var dirtyGroups = [];
-                    var any_groups_checked = 0;
+                // update groups
+                var dirtyGroups = [];
+                var any_groups_checked = 0;
+                $('#module-security').find('input[name=groups]').each(function() {
+                    var that = $(this);
+                    var defaultChecked = !!that.attr('checked');
+                    if (defaultChecked !== that.is(':checked')) {
+                        dirtyGroups.push({ name: that.val(), value: that.is(':checked') });
+                    }
+                    if (that.is(':checked')) {
+                        any_groups_checked = 1;
+                    }
+                });
+                $('#module-security .module-content')
+                    .html(data.groups)
+                    .addClass('attention');
+                $.each(dirtyGroups, function() {
+                    $('#module-security').find('input[value=' + this.name + ']').prop('checked', this.value);
+                });
+                // clear any default groups if user was making bug public
+                // unless the group is mandatory for the new product
+                if (!any_groups_checked) {
                     $('#module-security').find('input[name=groups]').each(function() {
                         var that = $(this);
-                        var defaultChecked = !!that.attr('checked');
-                        if (defaultChecked !== that.is(':checked')) {
-                            dirtyGroups.push({ name: that.val(), value: that.is(':checked') });
+                        if (!that.data('mandatory')) {
+                            that.prop('checked', false);
                         }
-                        if (that.is(':checked')) {
-                            any_groups_checked = 1;
-                        }
-                    });
-                    $('#module-security .module-content')
-                        .html(data.groups)
-                        .addClass('attention');
-                    $.each(dirtyGroups, function() {
-                        $('#module-security').find('input[value=' + this.name + ']').prop('checked', this.value);
                     });
-                    // clear any default groups if user was making bug public
-                    // unless the group is mandatory for the new product
-                    if (!any_groups_checked) {
-                        $('#module-security').find('input[name=groups]').each(function() {
-                            var that = $(this);
-                            if (!that.data('mandatory')) {
-                                that.prop('checked', false);
-                            }
-                        });
-                    }
-                },
-                function() {
-                    $('#product-throbber').hide();
-                    $('#component, #version, #target_milestone').attr('disabled', false);
                 }
-            );
+            } catch ({ message }) {
+                $('#io-error').html(message).show('fast');
+                $('#product-throbber').hide();
+                $('#component, #version, #target_milestone').attr('disabled', false);
+            }
         });
 
     // product/component search
@@ -1285,8 +1279,8 @@ $(function() {
 
     // comment preview
     var last_comment_text = '';
-    $('#comment-tabs li').click(function() {
-        var that = $(this);
+    $('#comment-tabs li').click(async event => {
+        var that = $(event.target);
         if (that.attr('aria-selected') === 'true')
             return;
 
@@ -1311,30 +1305,25 @@ $(function() {
             return;
         $('#preview-throbber').show();
         preview.html('');
-        bugzilla_ajax(
-            {
-                url: `${BUGZILLA.config.basepath}rest/bug/comment/render`,
-                type: 'POST',
-                data: { text: comment.val() },
-                hideError: true
-            },
-            function(data) {
-                $('#preview-throbber').hide();
-                preview.html(data.html);
-
-                // Highlight code if possible
-                if (Prism) {
-                  Prism.highlightAllUnder(preview.get(0));
-                }
-            },
-            function(message) {
-                $('#preview-throbber').hide();
-                var container = $('<div/>');
-                container.addClass('preview-error');
-                container.text(message);
-                preview.html(container);
+
+        try {
+            const { html } = await Bugzilla.API.post('bug/comment/render', { text: comment.val() });
+
+            $('#preview-throbber').hide();
+            preview.html(html);
+
+            // Highlight code if possible
+            if (Prism) {
+                Prism.highlightAllUnder(preview.get(0));
             }
-        );
+        } catch ({ message }) {
+            $('#preview-throbber').hide();
+            var container = $('<div/>');
+            container.addClass('preview-error');
+            container.text(message);
+            preview.html(container);
+        }
+
         last_comment_text = comment.val();
     }).keydown(function(event) {
         var that = $(this);
@@ -1395,7 +1384,7 @@ $(function() {
         });
 
     // Allow to attach pasted text directly
-    document.querySelector('#comment').addEventListener('paste', event => {
+    document.querySelector('#comment').addEventListener('paste', async event => {
       const text = event.clipboardData.getData('text');
       const lines = text.split(/(?:\r\n|\r|\n)/).length;
 
@@ -1415,23 +1404,21 @@ $(function() {
 
       event.preventDefault();
 
-      bugzilla_ajax({
-        type: 'POST',
-        url: `/rest/bug/${BUGZILLA.bug_id}/attachment`,
-        data: {
+      try {
+        await Bugzilla.API.post(`bug/${BUGZILLA.bug_id}/attachment`, {
           // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
           data: btoa(encodeURIComponent(text).replace(/%([0-9A-F]{2})/g, (m, p1) => String.fromCharCode(`0x${p1}`))),
           file_name: 'pasted.txt',
           summary,
           content_type: 'text/plain',
           comment: event.target.value.trim(),
-        },
-      }, () => {
+        });
+
         // Reload the page once upload is complete
         location.replace(`${BUGZILLA.config.basepath}show_bug.cgi?id=${BUGZILLA.bug_id}`);
-      }, message => {
+      } catch ({ message }) {
         window.alert(`Couldn’t upload the text as an attachment. Please try again later. Error: ${message}`);
-      });
+      }
     });
 
     restoreEditMode();
@@ -1444,13 +1431,15 @@ function confirmUnsafeURL(url) {
         'The full URL is:\n\n' + url + '\n\nContinue?');
 }
 
-function show_new_changes_indicator() {
-    const url = `${BUGZILLA.config.basepath}rest/bug_user_last_visit/${BUGZILLA.bug_id}`;
+async function show_new_changes_indicator() {
+    const url = `bug_user_last_visit/${BUGZILLA.bug_id}`;
+
+    try {
+        // Get the last visited timestamp
+        const data = await Bugzilla.API.get(url);
 
-    // Get the last visited timestamp
-    bugzilla_ajax({ url }, data => {
         // Save the current timestamp
-        bugzilla_ajax({ url, type: 'POST' });
+        Bugzilla.API.post(url);
 
         if (!data[0] || !data[0].last_visit_ts) {
             return;
@@ -1530,7 +1519,7 @@ function show_new_changes_indicator() {
 
         // TODO: Enable auto-scroll once the modal page layout is optimized
         // scroll_element_into_view($separator);
-    });
+    } catch (ex) {}
 }
 
 // fix url after bug creation/update
index ba7bd78f18e2c27e84fc78d3cfba812642d0f370..3289dca283067f078610219b65e0ae424ac945fa 100644 (file)
@@ -261,7 +261,7 @@ $(function() {
         $('#ctag-error').show();
     }
 
-    function deleteTag(event) {
+    async function deleteTag(event) {
         event.preventDefault();
         $('#ctag-error').hide();
 
@@ -280,21 +280,12 @@ $(function() {
         updateTagsMenu();
 
         // update bugzilla
-        bugzilla_ajax(
-            {
-                url: `${BUGZILLA.config.basepath}rest/bug/comment/${commentID}/tags`,
-                type: 'PUT',
-                data: { remove: [ tag ] },
-                hideError: true
-            },
-            function(data) {
-                renderTags(commentNo, data);
-                updateTagsMenu();
-            },
-            function(message) {
-                taggingError(commentNo, message);
-            }
-        );
+        try {
+            renderTags(commentNo, await Bugzilla.API.put(`bug/comment/${commentID}/tags`, { remove: [tag] }));
+            updateTagsMenu();
+        } catch ({ message }) {
+            taggingError(commentNo, message);
+        }
     }
     $('.comment-tag a').click(deleteTag);
 
@@ -322,30 +313,33 @@ $(function() {
         $(`.comment[data-no="${commentNo}"]`).attr('data-tags', tags.join(' '));
     }
 
-    var refreshXHR;
+    let abort_controller;
 
-    function refreshTags(commentNo, commentID) {
+    const refreshTags = async (commentNo, commentID) => {
         cancelRefresh();
-        refreshXHR = bugzilla_ajax(
-            {
-                url: `${BUGZILLA.config.basepath}rest/bug/comment/${commentID}?include_fields=tags`,
-                hideError: true
-            },
-            function(data) {
-                refreshXHR = false;
-                renderTags(commentNo, data.comments[commentID].tags);
-            },
-            function(message) {
-                refreshXHR = false;
+
+        try {
+            abort_controller = new AbortController();
+
+            const { signal } = abort_controller;
+            const { comments } = await Bugzilla.API.get(`bug/comment/${commentID}`, {
+              include_fields: ['tags'],
+            }, { signal });
+
+            renderTags(commentNo, comments[commentID].tags);
+        } catch ({ name, message }) {
+            if (name !== 'AbortError') {
                 taggingError(commentNo, message);
             }
-        );
+        } finally {
+            abort_controller = undefined;
+        }
     }
 
     function cancelRefresh() {
-        if (refreshXHR) {
-            refreshXHR.abort();
-            refreshXHR = false;
+        if (abort_controller) {
+            abort_controller.abort();
+            abort_controller = undefined;
         }
     }
 
@@ -353,31 +347,24 @@ $(function() {
         .devbridgeAutocomplete({
             appendTo: $('#main-inner'),
             forceFixPosition: true,
-            serviceUrl: function(query) {
-                return `${BUGZILLA.config.basepath}rest/bug/comment/tags/${encodeURIComponent(query)}`;
-            },
-            params: {
-                Bugzilla_api_token: (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-            },
             deferRequestBy: 250,
             minChars: 3,
             tabDisabled: true,
             autoSelectFirst: true,
             triggerSelectOnValidInput: false,
-            transformResult: function(response) {
-                response = $.parseJSON(response);
-                return {
-                    suggestions: $.map(response, function(tag) {
-                        return { value: tag };
-                    })
-                };
+            lookup: (query, done) => {
+                // Note: `async` doesn't work for this `lookup` function, so use a `Promise` chain instead
+                Bugzilla.API.get(`bug/comment/tags/${encodeURIComponent(query)}`)
+                    .then(data => data.map(tag => ({ value: tag })))
+                    .catch(() => [])
+                    .then(suggestions => done({ suggestions }));
             },
             formatResult: function(suggestion, currentValue) {
                 // disable <b> wrapping of matched substring
                 return suggestion.value.htmlEncode();
             }
         })
-        .keydown(function(event) {
+        .keydown(async event => {
             if (event.which === 27) {
                 event.preventDefault();
                 $('#ctag-close').click();
@@ -426,22 +413,13 @@ $(function() {
                 renderTags(commentNo, tags);
 
                 // update bugzilla
-                bugzilla_ajax(
-                    {
-                        url: `${BUGZILLA.config.basepath}rest/bug/comment/${commentID}/tags`,
-                        type: 'PUT',
-                        data: { add: addTags },
-                        hideError: true
-                    },
-                    function(data) {
-                        renderTags(commentNo, data);
-                        updateTagsMenu();
-                    },
-                    function(message) {
-                        taggingError(commentNo, message);
-                        refreshTags(commentNo, commentID);
-                    }
-                );
+                try {
+                    renderTags(commentNo, await Bugzilla.API.put(`bug/comment/${commentID}/tags`, { add: addTags }));
+                    updateTagsMenu();
+                } catch ({ message }) {
+                    taggingError(commentNo, message);
+                    refreshTags(commentNo, commentID);
+                }
             }
         });
 
@@ -602,12 +580,9 @@ Bugzilla.BugModal.Comments = class Comments {
     // Show text smaller than 50 KB
     if (is_text && size < 50000) {
       // Load text body
-      bugzilla_ajax({ url: `${BUGZILLA.config.basepath}rest/bug/attachment/${id}?include_fields=data` }, data => {
-        if (data.error) {
-          return;
-        }
-
-        const text = decodeURIComponent(escape(atob(data.attachments[id].data)));
+      try {
+        const { attachments } = await Bugzilla.API.get(`bug/attachment/${id}`, { include_fields: 'data' });
+        const text = decodeURIComponent(escape(atob(attachments[id].data)));
         const lang = is_patch ? 'diff' : type.match(/\w+$/)[0];
 
         $att.insertAdjacentHTML('beforeend', `
@@ -621,7 +596,7 @@ Bugzilla.BugModal.Comments = class Comments {
           Prism.highlightElement($att.querySelector('pre'));
           $att.querySelectorAll('pre a').forEach($a => $a.tabIndex = -1);
         }
-      });
+      } catch (ex) {}
     }
   }
 };
index 4272382b4dfbfebe7699686c1bdaa01b90b20226..9a9b411b578cb66c697f2da77bb61f39b88e3464 100644 (file)
@@ -13,13 +13,13 @@ var Bugzilla = Bugzilla || {};
 
 /**
  * Implement the one-click Component Watching functionality that can be added to any page.
- * @abstract
  */
 Bugzilla.ComponentWatching = class ComponentWatching {
   /**
    * Initialize a new ComponentWatching instance. Since constructors can't be async, use a separate function to move on.
    */
   constructor() {
+    this._method = 'component-watching';
     this.buttons = document.querySelectorAll('button.component-watching');
 
     // Check if the user is logged in and the API key is available. If not, remove the Watch buttons.
@@ -31,34 +31,30 @@ Bugzilla.ComponentWatching = class ComponentWatching {
   }
 
   /**
-   * Send a REST API request, and return the results in a Promise.
-   * @param {Object} [request={}] Request data. If omitted, all the current watches will be returned.
-   * @param {String} [path=''] Optional path to be appended to the request URL.
-   * @returns {Promise<Object|String>} Response data or error message.
+   * Retrieve the user's current watch list.
+   * @returns {Promise.<(Array.<Object>|Error)>} List of the current watches.
    */
-  async fetch(request = {}, path = '') {
-    request.url = `${BUGZILLA.config.basepath}rest/component-watching${path}`;
-
-    return new Promise((resolve, reject) => bugzilla_ajax(request, data => resolve(data), error => reject(error)));
+  async list() {
+    return Bugzilla.API.get(this._method);
   }
 
   /**
    * Start watching the current product or component.
    * @param {String} product Product name.
    * @param {String} [component=''] Component name. If omitted, all the components in the product will be watched.
-   * @returns {Promise<Object|String>} Response data or error message.
+   * @returns {Promise.<(Object|Error)>} Detail of the added watch.
    */
   async watch(product, component = '') {
-    return this.fetch({ type: 'POST', data: { product, component } });
+    return Bugzilla.API.post(this._method, { product, component });
   }
 
   /**
    * Stop watching the current product or component.
    * @param {Number} id ID of the watch to be removed.
-   * @returns {Promise<Object|String>} Response data or error message.
+   * @returns {Promise.<(Array.<Object>|Error)>} List of the current watches excluding the removed one.
    */
   async unwatch(id) {
-    return this.fetch({ type: 'DELETE' }, `/${id}`);
+    return Bugzilla.API.delete(`${this._method}/${id}`);
   }
 
   /**
@@ -195,7 +191,7 @@ Bugzilla.ComponentWatching = class ComponentWatching {
    */
   async init() {
     try {
-      const all_watches = await this.fetch();
+      const all_watches = await this.list();
 
       this.get_buttons().forEach($button => {
         const { product, component } = $button.dataset;
index bb2812297a3c780bef75460c865fcf268582657f..7bff8a7f2c267bb2ed35f4cbf5ef20ca6bf27196 100644 (file)
@@ -78,7 +78,7 @@ Bugzilla.InlineCommentEditor = class InlineCommentEditor {
    * Called whenever the Edit button is clicked. Hide the current comment and insert the inline comment editor instead.
    * @param {MouseEvent} event Click event.
    */
-  edit_button_onclick(event) {
+  async edit_button_onclick(event) {
     event.preventDefault();
 
     this.toggle_toolbar_buttons(true);
@@ -148,14 +148,11 @@ Bugzilla.InlineCommentEditor = class InlineCommentEditor {
     }
 
     // Retrieve the raw comment text
-    bugzilla_ajax({
-      url: `${BUGZILLA.config.basepath}rest/editcomments/comment/${this.comment_id}`,
-      hideError: true,
-    }, data => {
-      this.fetch_onload(data);
-    }, message => {
+    try {
+      this.fetch_onload(await Bugzilla.API.get(`editcomments/comment/${this.comment_id}`));
+    } catch ({ message }) {
       this.fetch_onerror(message);
-    });
+    }
   }
 
   /**
@@ -208,7 +205,7 @@ Bugzilla.InlineCommentEditor = class InlineCommentEditor {
   /**
    * Called whenever the Preview tab is clicked. Fetch and display the rendered comment.
    */
-  preview() {
+  async preview() {
     this.$preview.style.height = `${this.$textarea.scrollHeight}px`;
     this.$edit_tab.setAttribute('aria-selected', 'false');
     this.$edit_tabpanel.hidden = true;
@@ -219,13 +216,11 @@ Bugzilla.InlineCommentEditor = class InlineCommentEditor {
 
     this.render_message(this.str.loading);
 
-    bugzilla_ajax({
-      url: `${BUGZILLA.config.basepath}rest/bug/comment/render`,
-      type: 'POST',
-      hideError: true,
-      data: { id: BUGZILLA.bug_id, text: this.$textarea.value },
-    }, data => {
-      this.$preview.innerHTML = data.html;
+    try {
+      const text = this.$textarea.value;
+      const { html } = await Bugzilla.API.post('bug/comment/render', { id: BUGZILLA.bug_id, text });
+
+      this.$preview.innerHTML = html;
 
       // Highlight code if possible
       if (Prism) {
@@ -234,10 +229,10 @@ Bugzilla.InlineCommentEditor = class InlineCommentEditor {
 
       this.$preview.style.removeProperty('height');
       this.$preview.setAttribute('aria-busy', 'false');
-    }, () => {
+    } catch (ex) {
       this.render_message(this.str.preview_error);
       this.$preview.setAttribute('aria-busy', 'false');
-    });
+    }
   }
 
   /**
@@ -255,7 +250,7 @@ Bugzilla.InlineCommentEditor = class InlineCommentEditor {
   /**
    * Called whenever the Update Comment button is clicked. Upload the changes to the server.
    */
-  save() {
+  async save() {
     if (!this.edited) {
       return;
     }
@@ -264,19 +259,14 @@ Bugzilla.InlineCommentEditor = class InlineCommentEditor {
     this.$textarea.disabled = this.$save_button.disabled = this.$cancel_button.disabled = true;
     this.$status.textContent = this.str.saving;
 
-    bugzilla_ajax({
-      url: `${BUGZILLA.config.basepath}rest/editcomments/comment/${this.comment_id}`,
-      type: 'PUT',
-      hideError: true,
-      data: {
+    try {
+      this.save_onsuccess(await Bugzilla.API.put(`editcomments/comment/${this.comment_id}`, {
         new_comment: this.$textarea.value,
         is_hidden: this.$is_hidden_checkbox && this.$is_hidden_checkbox.checked ? 1 : 0,
-      },
-    }, data => {
-      this.save_onsuccess(data);
-    }, message => {
+      }));
+    } catch ({ message }) {
       this.save_onerror(message);
-    });
+    }
   }
 
   /**
index 42847367cf551bced001e9da446427d0c752b41a..e776c6d063333b4c9f4294279061f043fbb14ad9 100644 (file)
@@ -39,15 +39,7 @@ Bugzilla.CommentRevisionsManager = class CommentRevisionsManager {
     const change_when = $revision.dataset.revisedTime;
 
     $checkbox.addEventListener('change', () => {
-      bugzilla_ajax({
-        url: `${BUGZILLA.config.basepath}rest/editcomments/revision`,
-        type: 'PUT',
-        data: {
-          comment_id,
-          change_when,
-          is_hidden: $checkbox.checked ? 1 : 0,
-        },
-      });
+      Bugzilla.API.put('editcomments/revision', { comment_id, change_when, is_hidden: $checkbox.checked ? 1 : 0 });
     });
   }
 };
index 1b8b754d288e095de65b868502c738019f61590c..fbda10f8e96aa0679402bf75ccec39f9046db61f 100644 (file)
@@ -169,7 +169,7 @@ Bugzilla.FlagTypeComment = class FlagTypeComment {
    * Called whenever a flag selection is changed. Insert or remove a comment template.
    * @param {HTMLSelectElement} $select `<select>` element that the `change` event is fired.
    */
-  flag_onselect($select) {
+  async flag_onselect($select) {
     const id = Number($select.dataset.id);
     const { name } = $select.dataset;
     const state = $select.value;
@@ -189,10 +189,10 @@ Bugzilla.FlagTypeComment = class FlagTypeComment {
         this.$fieldset_wrapper.appendChild($fieldset);
 
         // Show any other patches that can be requested for approval
-        bugzilla_ajax({
-          url: `${BUGZILLA.config.basepath}rest/bug/${this.bug_id}/attachment?` +
-            'include_fields=id,summary,content_type,is_patch,is_obsolete',
-        }, ({ bugs }) => {
+        try {
+          const { bugs } = await Bugzilla.API.get(`bug/${this.bug_id}/attachment`, {
+            include_fields: ['id', 'summary', 'content_type', 'is_patch', 'is_obsolete'],
+          });
           const attachments = bugs ? bugs[this.bug_id] : [];
           const others = attachments.filter(att => att.id !== this.attachment_id && !att.is_obsolete &&
             (att.is_patch || this.extra_patch_types.includes(att.content_type)));
@@ -208,7 +208,7 @@ Bugzilla.FlagTypeComment = class FlagTypeComment {
               </td></tr>
             `);
           }
-        });
+        } catch (ex) {}
       }
     }
 
@@ -269,20 +269,18 @@ Bugzilla.FlagTypeComment = class FlagTypeComment {
     });
 
     // Request approval for other patches if any
-    await Promise.all([...this.inserted_fieldsets].map($fieldset => new Promise(resolve => {
+    await Promise.all([...this.inserted_fieldsets].map($fieldset => new Promise(async resolve => {
       const ids = [...$fieldset.querySelectorAll('tr.other-patches input:checked')]
         .map($input => Number($input.dataset.id));
       const flags = this.find_selectors($fieldset).map($select => ({ name: $select.dataset.name, status: '?' }));
 
       if (ids.length && flags.length) {
-        bugzilla_ajax({
-          type: 'PUT',
-          url: `${BUGZILLA.config.basepath}rest/bug/attachment/${ids[0]}`,
-          data: { ids, flags },
-        }, () => resolve(), () => resolve());
-      } else {
-        resolve();
+        try {
+          await Bugzilla.API.put(`bug/attachment/${ids[0]}`, { ids, flags });
+        } catch (ex) {}
       }
+
+      resolve();
     })));
 
     // Collect bug flags from checkboxes
@@ -291,12 +289,12 @@ Bugzilla.FlagTypeComment = class FlagTypeComment {
 
     // Update bug flags if needed
     if (bug_flags.length) {
-      await new Promise(resolve => {
-        bugzilla_ajax({
-          type: 'PUT',
-          url: `${BUGZILLA.config.basepath}rest/bug/${this.bug_id}`,
-          data: { flags: bug_flags },
-        }, () => resolve(), () => resolve());
+      await new Promise(async resolve => {
+        try {
+          await Bugzilla.API.put(`bug/${this.bug_id}`, { flags: bug_flags });
+        } catch (ex) {}
+
+        resolve();
       });
     }
 
index 2f26849850e8cdedfae8a81bcc215dbe64b52ed0..fae31d4a5af3a65497bfd89f4d133a2fe8cf4c43 100644 (file)
@@ -115,7 +115,6 @@ var webdev = {
 
 var product = {
   details: false,
-  _counter: 0,
   _loaded: '',
   _preselectedComponent: '',
 
@@ -173,7 +172,7 @@ var product = {
     return result;
   },
 
-  setName: function(productName) {
+  setName: async function(productName) {
     if (productName == this.getName() && this.details)
       return;
 
@@ -215,55 +214,25 @@ var product = {
     // grab the product information
     this.details = false;
     this._loaded = productName;
-    YAHOO.util.Connect.setDefaultPostHeader('application/json; charset=UTF-8');
-    YAHOO.util.Connect.asyncRequest(
-      'POST',
-      `${BUGZILLA.config.basepath}jsonrpc.cgi`,
-      {
-        success: function(res) {
-          try {
-            var data = JSON.parse(res.responseText);
-            if (data.error)
-              throw(data.error.message);
-            if (data.result.products.length == 0)
-              document.location.href = `${BUGZILLA.config.basepath}enter_bug.cgi?format=guided`;
-            product.details = data.result.products[0];
-            bugForm.onProductUpdated();
-          } catch (err) {
-            product.details = false;
-            bugForm.onProductUpdated();
-            if (err) {
-              alert('Failed to retrieve components for product "' +
-                productName + '":' + "\n\n" + err);
-              if (console)
-                console.error(err);
-            }
-          }
-        },
-        failure: function(res) {
-          this._loaded = '';
-          product.details = false;
-          bugForm.onProductUpdated();
-          if (res.responseText) {
-            alert('Failed to retrieve components for product "' +
-              productName + '":' + "\n\n" + res.responseText);
-            if (console)
-              console.error(res);
-          }
-        }
-      },
-      JSON.stringify({
-        version: "1.1",
-        method: "Product.get",
-        id: ++this._counter,
-        params: {
-          names: [productName],
-          exclude_fields: ['internals', 'milestones', 'components.flag_types'],
-          Bugzilla_api_token : (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-        }
+
+    try {
+      const { products } = await Bugzilla.API.get('product', {
+        names: [productName],
+        exclude_fields: ['internals', 'milestones', 'components.flag_types'],
+      });
+
+      if (products.length) {
+        product.details = products[0];
+        bugForm.onProductUpdated();
+      } else {
+        document.location.href = `${BUGZILLA.config.basepath}enter_bug.cgi?format=guided`;
       }
-      )
-    );
+    } catch ({ message }) {
+      this._loaded = '';
+      product.details = false;
+      bugForm.onProductUpdated();
+      alert(`Failed to retrieve components for product "${productName}"\n\n${message}`);
+    }
   }
 };
 
@@ -280,7 +249,6 @@ var otherProducts = {
 // duplicates step
 
 var dupes = {
-  _counter: 0,
   _dataTable: null,
   _dataTableColumns: null,
   _elSummary: null,
@@ -311,37 +279,10 @@ var dupes = {
   },
 
   _initDataTable: function() {
-    var dataSource = new YAHOO.util.XHRDataSource(`${BUGZILLA.config.basepath}jsonrpc.cgi`);
-    dataSource.connTimeout = 15000;
-    dataSource.connMethodPost = true;
-    dataSource.connXhrMode = "cancelStaleRequests";
-    dataSource.maxCacheEntries = 3;
-    dataSource.responseSchema = {
-      resultsList : "result.bugs",
-      metaFields : { error: "error", jsonRpcId: "id" }
-    };
-    // DataSource can't understand a JSON-RPC error response, so
-    // we have to modify the result data if we get one.
-    dataSource.doBeforeParseData =
-      function(oRequest, oFullResponse, oCallback) {
-        if (oFullResponse.error) {
-          oFullResponse.result = {};
-          oFullResponse.result.bugs = [];
-          if (console)
-            console.error("JSON-RPC error:", oFullResponse.error);
-        }
-        return oFullResponse;
-      };
-    dataSource.subscribe('dataErrorEvent',
-      function() {
-        dupes._currentSearchQuery = '';
-      }
-    );
-
     this._dataTable = new YAHOO.widget.DataTable(
       'dupes_list',
       this._dataTableColumns,
-      dataSource,
+      new YAHOO.util.LocalDataSource([]), // Dummy data source
       {
         initialLoad: false,
         MSG_EMPTY: 'No similar issues found.',
@@ -412,7 +353,7 @@ var dupes = {
     el.appendChild(button);
   },
 
-  updateFollowing: function(el, bugID, bugStatus, button, follow) {
+  updateFollowing: async function(el, bugID, bugStatus, button, follow) {
     button.disabled = true;
     button.innerHTML = 'Updating...';
 
@@ -423,34 +364,13 @@ var dupes = {
       ccObject = { remove: [ guided.currentUser ] };
     }
 
-    YAHOO.util.Connect.setDefaultPostHeader('application/json; charset=UTF-8');
-    YAHOO.util.Connect.asyncRequest(
-      'POST',
-      `${BUGZILLA.config.basepath}jsonrpc.cgi`,
-      {
-        success: function(res) {
-          var data = JSON.parse(res.responseText);
-          if (data.error)
-            throw(data.error.message);
-          dupes._buildCcHTML(el, bugID, bugStatus, follow);
-        },
-        failure: function(res) {
-          dupes._buildCcHTML(el, bugID, bugStatus, !follow);
-          if (res.responseText)
-            alert("Update failed:\n\n" + res.responseText);
-        }
-      },
-      JSON.stringify({
-        version: "1.1",
-        method: "Bug.update",
-        id: ++this._counter,
-        params: {
-          ids: [ bugID ],
-          cc : ccObject,
-          Bugzilla_api_token: (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-        }
-      })
-    );
+    try {
+      await Bugzilla.API.put(`bug/${bugID}`, { ids: [bugID], cc: ccObject });
+      dupes._buildCcHTML(el, bugID, bugStatus, follow);
+    } catch ({ message }) {
+      dupes._buildCcHTML(el, bugID, bugStatus, !follow);
+      alert(`Update failed:\n\n${message}`);
+    }
   },
 
   reset: function() {
@@ -524,7 +444,7 @@ var dupes = {
     dupes._elSearch.disabled = dupes._elSummary.value.trim() == '';
   },
 
-  _doSearch: function() {
+  _doSearch: async function() {
     if (dupes.getSummary().length < 4) {
       alert('The summary must be at least 4 characters long.');
       return;
@@ -549,48 +469,38 @@ var dupes = {
         ' width="16" height="11">',
         YAHOO.widget.DataTable.CLASS_LOADING
       );
-      var json_object = {
-          version: "1.1",
-          method: "Bug.possible_duplicates",
-          id: ++dupes._counter,
-          params: {
-              product: product._getNameAndRelated(),
-              summary: dupes.getSummary(),
-              limit: 12,
-              include_fields: [ "id", "summary", "status", "resolution",
-                "update_token", "cc", "component" ],
-              Bugzilla_api_token: (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-          }
-      };
-
-      dupes._dataTable.getDataSource().sendRequest(
-        JSON.stringify(json_object),
-        {
-          success: dupes._onDupeResults,
-          failure: dupes._onDupeResults,
-          scope: dupes._dataTable,
-          argument: dupes._dataTable.getState()
-        }
-      );
 
       Dom.get('dupes_continue_button_top').disabled = true;
       Dom.get('dupes_continue_button_bottom').disabled = true;
       Dom.removeClass('dupes_continue', 'hidden');
+
+      let data;
+
+      try {
+        const { bugs } = await Bugzilla.API.get('bug/possible_duplicates', {
+          product: product._getNameAndRelated(),
+          summary: dupes.getSummary(),
+          limit: 12,
+          include_fields: ['id', 'summary', 'status', 'resolution', 'update_token', 'cc', 'component'],
+        });
+
+        data = { results: bugs };
+      } catch (ex) {
+        dupes._currentSearchQuery = '';
+        data = { error: true };
+      }
+
+      Dom.removeClass('advanced', 'hidden');
+      Dom.removeClass('dupes_continue_button_top', 'hidden');
+      Dom.get('dupes_continue_button_top').disabled = false;
+      Dom.get('dupes_continue_button_bottom').disabled = false;
+      dupes._dataTable.onDataReturnInitializeTable('', data);
     } catch(err) {
       if (console)
         console.error(err.message);
     }
   },
 
-  _onDupeResults: function(sRequest, oResponse, oPayload) {
-    Dom.removeClass('advanced', 'hidden');
-    Dom.removeClass('dupes_continue_button_top', 'hidden');
-    Dom.get('dupes_continue_button_top').disabled = false;
-    Dom.get('dupes_continue_button_bottom').disabled = false;
-    dupes._dataTable.onDataReturnInitializeTable(sRequest, oResponse,
-      oPayload);
-  },
-
   getSummary: function() {
     var summary = this._elSummary.value.trim();
     // work around chrome bug
index cc6b96c1a4493d6c97fa87fa1015c22b885a1b12..4023791a34c17953077eec3e9d392dce0f440652 100644 (file)
@@ -156,7 +156,14 @@ sub bug_interest_unmark {
 }
 
 sub rest_resources {
-  return [qr{^/bug_interest_unmark$}, {PUT => {method => 'bug_interest_unmark'}}];
+  return [
+    # REST API v1: Expose `bug_interest_unmark` method without prefix for backward compatibility
+    qr{^/(mydashboard/)?bug_interest_unmark$}, {PUT => {method => 'bug_interest_unmark'}},
+    # Other methods are new, so require the prefix
+    qr{^/mydashboard/run_bug_query$}, {GET => {method => 'run_bug_query'}},
+    qr{^/mydashboard/run_flag_query$}, {GET => {method => 'run_flag_query'}},
+    qr{^/mydashboard/run_last_changes$}, {GET => {method => 'run_last_changes'}},
+  ];
 }
 
 1;
index d1c665d29d577d586868de5a9c9177ba5775d40c..b9c71691225f7fe98be25a81b262e5a4b951ec18 100644 (file)
@@ -11,59 +11,16 @@ $(function () {
     YUI({
         base: 'js/yui3/',
         combine: false
-    }).use("node", "datatable", "datatable-sort",
-        "datatable-datasource", "datasource-io", "datasource-jsonschema", function(Y) {
+    }).use('node', 'datatable', 'datatable-sort', 'escape', function(Y) {
         // Common
-        var counter = 0;
-        var dataSource = {
-            requestee: null,
-            requester: null
-        };
         var dataTable = {
             requestee: null,
             requester: null
         };
 
-        var updateFlagTable = function(type) {
+        var updateFlagTable = async type => {
             if (!type) return;
 
-            counter = counter + 1;
-
-            var callback = {
-                success: function(e) {
-                    if (e.response) {
-                        Y.one('#' + type + '_loading').addClass('bz_default_hidden');
-                        Y.one('#' + type + '_count_refresh').removeClass('bz_default_hidden');
-                        Y.one("#" + type + "_flags_found").setHTML(
-                            e.response.results.length +
-                            ' request' + (e.response.results.length == 1 ? '' : 's') +
-                            ' found');
-                        dataTable[type].set('data', e.response.results);
-                    }
-                },
-                failure: function(o) {
-                    Y.one('#' + type + '_loading').addClass('bz_default_hidden');
-                    Y.one('#' + type + '_count_refresh').removeClass('bz_default_hidden');
-                    if (o.error && o.error.message) {
-                        alert("Failed to load requests:\n\n" + o.error.message);
-                    } else {
-                        alert("Failed to load requests");
-                    }
-                }
-            };
-
-            var json_object = {
-                version: "1.1",
-                method:  "MyDashboard.run_flag_query",
-                id:      counter,
-                params:  {
-                    type : type,
-                    Bugzilla_api_token : (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-                }
-            };
-
-            var stringified = JSON.stringify(json_object);
-
             Y.one('#' + type + '_loading').removeClass('bz_default_hidden');
             Y.one('#' + type + '_count_refresh').addClass('bz_default_hidden');
 
@@ -71,14 +28,23 @@ $(function () {
             dataTable[type].render("#" + type + "_table");
             dataTable[type].showMessage('loadingMessage');
 
-            dataSource[type].sendRequest({
-                request: stringified,
-                cfg: {
-                    method:  "POST",
-                    headers: { 'Content-Type': 'application/json' }
-                },
-                callback: callback
-            });
+            try {
+                const { result } = await Bugzilla.API.get('mydashboard/run_flag_query', { type });
+                const results = result[type];
+
+                Y.one(`#${type}_loading`).addClass('bz_default_hidden');
+                Y.one(`#${type}_count_refresh`).removeClass('bz_default_hidden');
+                Y.one(`#${type}_flags_found`)
+                    .setHTML(`${results.length} ${(results.length === 1 ? 'request' : 'requests')} found`);
+
+                dataTable[type].set('data', results);
+                dataTable[type].render(`#${type}_table`);
+            } catch ({ message }) {
+                Y.one(`#${type}_loading`).addClass('bz_default_hidden');
+                Y.one(`#${type}_count_refresh`).removeClass('bz_default_hidden');
+
+                alert(`Failed to load requests:\n\n${message}`);
+            }
         };
 
         var loadBugList = function(type) {
@@ -132,16 +98,6 @@ $(function () {
         };
 
         // Requestee
-        dataSource.requestee = new Y.DataSource.IO({ source: `${BUGZILLA.config.basepath}jsonrpc.cgi` });
-        dataSource.requestee.on('error', function(e) {
-            try {
-                var response = JSON.parse(e.data.responseText);
-                if (response.error)
-                    e.error.message = response.error.message;
-            } catch(ex) {
-                // ignore
-            }
-        });
         dataTable.requestee = new Y.DataTable({
             columns: [
                 { key: "requester", label: "Requester", sortable: true },
@@ -159,20 +115,6 @@ $(function () {
 
         dataTable.requestee.plug(Y.Plugin.DataTableSort);
 
-        dataTable.requestee.plug(Y.Plugin.DataTableDataSource, {
-            datasource: dataSource.requestee
-        });
-
-        dataSource.requestee.plug(Y.Plugin.DataSourceJSONSchema, {
-            schema: {
-                resultListLocator: "result.result.requestee",
-                resultFields: ["requester", "type", "attach_id", "is_patch", "bug_id",
-                            "bug_status", "bug_summary", "updated", "updated_fancy"]
-            }
-        });
-
-        dataTable.requestee.render("#requestee_table");
-
         Y.one('#requestee_refresh').on('click', function(e) {
             updateFlagTable('requestee');
         });
@@ -181,16 +123,6 @@ $(function () {
         });
 
         // Requester
-        dataSource.requester = new Y.DataSource.IO({ source: `${BUGZILLA.config.basepath}jsonrpc.cgi` });
-        dataSource.requester.on('error', function(e) {
-            try {
-                var response = JSON.parse(e.data.responseText);
-                if (response.error)
-                    e.error.message = response.error.message;
-            } catch(ex) {
-                // ignore
-            }
-        });
         dataTable.requester = new Y.DataTable({
             columns: [
                 { key:"requestee", label:"Requestee", sortable:true,
@@ -209,18 +141,6 @@ $(function () {
 
         dataTable.requester.plug(Y.Plugin.DataTableSort);
 
-        dataTable.requester.plug(Y.Plugin.DataTableDataSource, {
-            datasource: dataSource.requester
-        });
-
-        dataSource.requester.plug(Y.Plugin.DataSourceJSONSchema, {
-            schema: {
-                resultListLocator: "result.result.requester",
-                resultFields: ["requestee", "type", "attach_id", "is_patch", "bug_id",
-                            "bug_status", "bug_summary", "updated", "updated_fancy"]
-            }
-        });
-
         Y.one('#requester_refresh').on('click', function(e) {
             updateFlagTable('requester');
         });
index 1dd082bf38bbc073d94d8d2be69654086a91b344..6a1bc039834e4fcda5d5e16a4fadc9f89a6a01d3 100644 (file)
@@ -22,13 +22,9 @@ $(function() {
                 patterns: { 'gallery-': {} }
             }
         }
-    }).use("node", "datatable", "datatable-sort", "datatable-message",
-        "datatable-datasource", "datasource-io", "datasource-jsonschema", "cookie",
+    }).use("node", "datatable", "datatable-sort", "datatable-message", "cookie",
         "gallery-datatable-row-expansion-bmo", "handlebars", function(Y) {
-        var counter          = 0,
-            bugQueryTable    = null,
-            bugQuery         = null,
-            lastChangesQuery = null,
+        var bugQueryTable    = null,
             lastChangesCache = {},
             default_query    = "assignedbugs";
 
@@ -48,73 +44,9 @@ $(function() {
             }
         }
 
-        var bugQuery = new Y.DataSource.IO({ source: `${BUGZILLA.config.basepath}jsonrpc.cgi` });
-
-        bugQuery.plug(Y.Plugin.DataSourceJSONSchema, {
-            schema: {
-                resultListLocator: "result.result.bugs",
-                resultFields: ["bug_id", "bug_type", "changeddate", "changeddate_fancy",
-                            "bug_status", "short_desc", "changeddate_api" ],
-                metaFields: {
-                    description: "result.result.description",
-                    heading:     "result.result.heading",
-                    buffer:      "result.result.buffer",
-                    mark_read:   "result.result.mark_read"
-                }
-            }
-        });
-
-        bugQuery.on('error', function(e) {
-            try {
-                var response = JSON.parse(e.data.responseText);
-                if (response.error)
-                    e.error.message = response.error.message;
-            } catch(ex) {
-                // ignore
-            }
-        });
-
-        var bugQueryCallback = {
-            success: function(e) {
-                if (e.response) {
-                    Y.one('#query_loading').addClass('bz_default_hidden');
-                    Y.one('#query_count_refresh').removeClass('bz_default_hidden');
-                    Y.one("#query_container .query_description").setHTML(e.response.meta.description);
-                    Y.one("#query_container .query_heading").setHTML(e.response.meta.heading);
-                    Y.one("#query_bugs_found").setHTML(
-                        `<a href="${BUGZILLA.config.basepath}buglist.cgi?${e.response.meta.buffer}" target="_blank">` +
-                        `${e.response.results.length} bugs found</a>`);
-                    bugQueryTable.set('data', e.response.results);
-
-                    var mark_read = e.response.meta.mark_read;
-                    if (mark_read) {
-                        Y.one('#query_markread').setHTML( mark_read );
-                        Y.one('#bar_markread').removeClass('bz_default_hidden');
-                        Y.one('#query_markread_text').setHTML( mark_read );
-                        Y.one('#query_markread').removeClass('bz_default_hidden');
-                    }
-                    else {
-                        Y.one('#bar_markread').addClass('bz_default_hidden');
-                        Y.one('#query_markread').addClass('bz_default_hidden');
-                    }
-                    Y.one('#query_markread_text').addClass('bz_default_hidden');
-                }
-            },
-            failure: function(o) {
-                Y.one('#query_loading').addClass('bz_default_hidden');
-                Y.one('#query_count_refresh').removeClass('bz_default_hidden');
-                if (o.error) {
-                    alert("Failed to load bug list from Bugzilla:\n\n" + o.error.message);
-                } else {
-                    alert("Failed to load bug list from Bugzilla.");
-                }
-            }
-        };
-
-        var updateQueryTable = function(query_name) {
+        var updateQueryTable = async query_name => {
             if (!query_name) return;
 
-            counter = counter + 1;
             lastChangesCache = {};
 
             Y.one('#query_loading').removeClass('bz_default_hidden');
@@ -123,23 +55,37 @@ $(function() {
             bugQueryTable.render("#query_table");
             bugQueryTable.showMessage('loadingMessage');
 
-            var bugQueryParams = {
-                version: "1.1",
-                method:  "MyDashboard.run_bug_query",
-                id:      counter,
-                params:  { query : query_name,
-                        Bugzilla_api_token : (BUGZILLA.api_token ? BUGZILLA.api_token : '')
+            try {
+                const { result } = await Bugzilla.API.get('mydashboard/run_bug_query', { query: query_name });
+                const { buffer, bugs, description, heading, mark_read } = result;
+
+                Y.one('#query_loading').addClass('bz_default_hidden');
+                Y.one('#query_count_refresh').removeClass('bz_default_hidden');
+                Y.one("#query_container .query_description").setHTML(description);
+                Y.one("#query_container .query_heading").setHTML(heading);
+                Y.one("#query_bugs_found").setHTML(`<a href="${BUGZILLA.config.basepath}buglist.cgi?${buffer}" ` +
+                    `target="_blank">${bugs.length} bugs found</a>`);
+
+                bugQueryTable.set('data', bugs);
+                bugQueryTable.render("#query_table");
+
+                if (mark_read) {
+                    Y.one('#query_markread').setHTML(mark_read);
+                    Y.one('#bar_markread').removeClass('bz_default_hidden');
+                    Y.one('#query_markread_text').setHTML(mark_read);
+                    Y.one('#query_markread').removeClass('bz_default_hidden');
+                } else {
+                    Y.one('#bar_markread').addClass('bz_default_hidden');
+                    Y.one('#query_markread').addClass('bz_default_hidden');
                 }
-            };
 
-            bugQuery.sendRequest({
-                request: JSON.stringify(bugQueryParams),
-                cfg: {
-                    method:  "POST",
-                    headers: { 'Content-Type': 'application/json' }
-                },
-                callback: bugQueryCallback
-            });
+                Y.one('#query_markread_text').addClass('bz_default_hidden');
+            } catch ({ message }) {
+                Y.one('#query_loading').addClass('bz_default_hidden');
+                Y.one('#query_count_refresh').removeClass('bz_default_hidden');
+
+                alert(`Failed to load bug list from Bugzilla:\n\n${message}`);
+            }
         };
 
         var updatedFormatter = function(o) {
@@ -151,25 +97,6 @@ $(function() {
           `<a href="${BUGZILLA.config.basepath}show_bug.cgi?id=${data.bug_id}" target="_blank">
           ${isNaN(value) ? value.htmlEncode().wbr() : value}</a>`;
 
-        lastChangesQuery = new Y.DataSource.IO({ source: `${BUGZILLA.config.basepath}jsonrpc.cgi` });
-
-        lastChangesQuery.plug(Y.Plugin.DataSourceJSONSchema, {
-            schema: {
-                resultListLocator: "result.results",
-                resultFields: ["last_changes"],
-            }
-        });
-
-        lastChangesQuery.on('error', function(e) {
-            try {
-                var response = JSON.parse(e.data.responseText);
-                if (response.error)
-                    e.error.message = response.error.message;
-            } catch(ex) {
-                // ignore
-            }
-        });
-
         bugQueryTable = new Y.DataTable({
             columns: [
                 { key: Y.Plugin.DataTableRowExpansion.column_key, label: ' ', sortable: false },
@@ -196,46 +123,22 @@ $(function() {
 
         bugQueryTable.plug(Y.Plugin.DataTableRowExpansion, {
             uniqueIdKey: 'bug_id',
-            template: function(data) {
-                var bug_id = data.bug_id;
+            template: ({ bug_id, changeddate_api }) => {
+                // Note: `async` doesn't work for this `template` function, so use an inner `async` function instead
+                if (!lastChangesCache[bug_id]) {
+                    try {
+                        (async () => {
+                            const { results } =
+                                await Bugzilla.API.get('mydashboard/run_last_changes', { bug_id, changeddate_api });
+                            const { last_changes } = results[0];
 
-                var lastChangesCallback = {
-                    success: function(e) {
-                        if (e.response) {
-                            var last_changes = e.response.results[0].last_changes;
                             last_changes['bug_id'] = bug_id;
                             lastChangesCache[bug_id] = last_changes;
                             Y.one('#last_changes_stub_' + bug_id).setHTML(last_changes_template(last_changes));
-                        }
-                    },
-                    failure: function(o) {
-                        if (o.error) {
-                            alert("Failed to load last changes from Bugzilla:\n\n" + o.error.message);
-                        } else {
-                            alert("Failed to load last changes from Bugzilla.");
-                        }
+                        })();
+                    } catch ({ message }) {
+                        alert(`Failed to load last changes from Bugzilla:\n\n${message}`);
                     }
-                };
-
-                if (!lastChangesCache[bug_id]) {
-                    var lastChangesParams = {
-                        version: "1.1",
-                        method:  "MyDashboard.run_last_changes",
-                        params:  {
-                            bug_id: data.bug_id,
-                            changeddate_api: data.changeddate_api,
-                            Bugzilla_api_token : (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-                        }
-                    };
-
-                    lastChangesQuery.sendRequest({
-                        request: JSON.stringify(lastChangesParams),
-                        cfg: {
-                            method:  "POST",
-                            headers: { 'Content-Type': 'application/json' }
-                        },
-                        callback: lastChangesCallback
-                    });
 
                     return stub_template({bug_id: bug_id});
                 }
@@ -248,10 +151,6 @@ $(function() {
 
         bugQueryTable.plug(Y.Plugin.DataTableSort);
 
-        bugQueryTable.plug(Y.Plugin.DataTableDataSource, {
-            datasource: bugQuery
-        });
-
         // Initial load
         Y.on("contentready", function (e) {
             updateQueryTable(default_query);
@@ -281,8 +180,8 @@ $(function() {
             for (var i = 0, l = data.size(); i < l; i++) {
                 bug_ids.push(data.item(i).get('bug_id'));
             }
-            YAHOO.bugzilla.bugUserLastVisit.update(bug_ids);
-            YAHOO.bugzilla.bugInterest.unmark(bug_ids);
+            Bugzilla.API.post('bug_user_last_visit', { ids: bug_ids });
+            Bugzilla.API.put('mydashboard/bug_interest_unmark', { bug_ids });
         });
 
         Y.one('#query_buglist').on('click', function(e) {
index 10a390ab06c627b60b840aeb1ed19c212c3b846f..1666c67c7eec097925569ca1f911a56f00226e04 100644 (file)
@@ -8,7 +8,7 @@
 
 var Phabricator = {};
 
-Phabricator.getBugRevisions = function() {
+Phabricator.getBugRevisions = async () => {
     var phabUrl = $('.phabricator-revisions').data('phabricator-base-uri');
     var tr      = $('<tr/>');
     var td      = $('<td/>');
@@ -84,32 +84,19 @@ Phabricator.getBugRevisions = function() {
         errRow.removeClass('bz_default_hidden');
     }
 
-    var $getUrl = '/rest/phabbugz/bug_revisions/' + BUGZILLA.bug_id +
-                  '?Bugzilla_api_token=' + BUGZILLA.api_token;
+    try {
+        const { revisions } = await Bugzilla.API.get(`phabbugz/bug_revisions/${BUGZILLA.bug_id}`);
 
-    $.getJSON($getUrl, function(data) {
-        if (data.revisions.length === 0) {
-            displayLoadError('none returned from server');
-        } else {
-            var i = 0;
-            for (; i < data.revisions.length; i++) {
-                tbody.append(revisionRow(data.revisions[i]));
-            }
-        }
-        tbody.find('.phabricator-loading-row').addClass('bz_default_hidden');
-    }).fail(function(jqXHR, textStatus, errorThrown) {
-        var errStr;
-        if (jqXHR.responseJSON && jqXHR.responseJSON.err &&
-            jqXHR.responseJSON.err.msg) {
-            errStr = jqXHR.responseJSON.err.msg;
-        } else if (errorThrown) {
-            errStr = errorThrown;
+        if (revisions.length) {
+            revisions.forEach(rev => tbody.append(revisionRow(rev)));
         } else {
-            errStr = 'unknown';
+            displayLoadError('none returned from server');
         }
-        displayLoadError(errStr);
-        tbody.find('.phabricator-loading-row').addClass('bz_default_hidden');
-    });
+    } catch ({ message }) {
+        displayLoadError(message);
+    }
+
+    tbody.find('.phabricator-loading-row').addClass('bz_default_hidden');
 };
 
 $().ready(function() {
index e4d9092c179e9957db1e92082fba804380dfdeb9..dcad05820fabde40f8ae510210b5f182cb79da8a 100644 (file)
@@ -59,19 +59,11 @@ $(function() {
     $('.prod_comp_search')
         .each(function() {
             var that = $(this);
-            var params = {
-                limit: (that.data('max_results') + 1)
-            };
-            if (BUGZILLA.api_token) {
-                params.Bugzilla_api_token = BUGZILLA.api_token;
-            }
+            const limit = that.data('max_results') + 1;
+
             that.devbridgeAutocomplete({
                 appendTo: $('#main-inner'),
                 forceFixPosition: true,
-                serviceUrl: function(query) {
-                    return `${BUGZILLA.config.basepath}rest/prod_comp_search/find/${encodeURIComponent(query)}`;
-                },
-                params: params,
                 deferRequestBy: 250,
                 minChars: 3,
                 maxHeight: 500,
@@ -79,28 +71,15 @@ $(function() {
                 autoSelectFirst: true,
                 triggerSelectOnValidInput: false,
                 width: '',
-                transformResult: function(response) {
-                    response = $.parseJSON(response);
-                    if (response.error) {
-                        that.data('error', response.message);
-                        return { suggestions: [] };
-                    }
-                    return {
-                        suggestions: $.map(response.products, function(dataItem) {
-                            if (dataItem.component) {
-                                return {
-                                    value: dataItem.product + ' :: ' + dataItem.component,
-                                    data : dataItem
-                                };
-                            }
-                            else {
-                                return {
-                                    value: dataItem.product,
-                                    data : dataItem
-                                };
-                            }
-                        })
-                    };
+                lookup: (query, done) => {
+                    // Note: `async` doesn't work for this `lookup` function, so use a `Promise` chain instead
+                    Bugzilla.API.get(`prod_comp_search/find/${encodeURIComponent(query)}`, { limit })
+                        .then(({ products }) => products.map(item => ({
+                            value: `${item.product}${item.component ? ` :: ${item.component}` : ''}`,
+                            data : item,
+                        })))
+                        .catch(() => [])
+                        .then(suggestions => done({ suggestions }));
                 },
                 formatResult: function(suggestion, currentValue) {
                     var value = (suggestion.data.component ? suggestion.data.component : suggestion.data.product);
@@ -224,19 +203,21 @@ Bugzilla.FrequentComponents = class FrequentComponents {
    * @returns {Promise} Results or error.
    */
   async fetch() {
-    return new Promise((resolve, reject) => bugzilla_ajax({
-      url: `${BUGZILLA.config.basepath}rest/prod_comp_search/frequent`
-    }, ({ results }) => {
+    try {
+      const { results } = await Bugzilla.API.get('prod_comp_search/frequent');
+
       if (!results) {
-        reject(new Error('Your frequent components could not be retrieved.'));
-      } else if (!results.length) {
-        reject(new Error(('Your frequent components could not be found.')));
-      } else {
-        resolve(results);
+        return Promise.reject(new Error('Your frequent components could not be retrieved.'));
       }
-    }, () => {
-      reject(new Error('Your frequent components could not be retrieved.'));
-    }));
+
+      if (!results.length) {
+        return Promise.reject(new Error('Your frequent components could not be found.'));
+      }
+
+      return Promise.resolve(results);
+    } catch (ex) {
+      return Promise.reject(new Error('Your frequent components could not be retrieved.'));
+    }
   }
 };
 
index e1820b7d9953f3c6cd873287358964e961581633..6716c748b487b249e11e0703b1b5ed720cf9489f 100644 (file)
@@ -25,7 +25,7 @@ function validateAndSubmit() {
     return true;
 }
 
-function getBugInfo (evt) {
+async function getBugInfo (evt) {
     var bug_id = parseInt(this.value);
     var div = $("#bug_info");
 
@@ -42,33 +42,22 @@ function getBugInfo (evt) {
 
     div.text('Getting bug info...');
 
-    var url = `${BUGZILLA.config.basepath}rest/bug/${bug_id}?` +
-              `include_fields=product,component,status,summary&Bugzilla_api_token=${BUGZILLA.api_token}`;
-    $.getJSON(url).done(function(data) {
-        var bug_message = "";
-        if (data) {
-            if (data.bugs[0].product !== 'Mozilla Reps'
-                || data.bugs[0].component !== 'Budget Requests')
-            {
-                bug_message = "You can only attach budget payment " +
-                    "information to bugs under the product " +
-                    "'Mozilla Reps' and component 'Budget Requests'.";
-            }
-            else {
-                bug_message = "Bug " + bug_id + " - " + data.bugs[0].status +
-                    " - " + data.bugs[0].summary;
-            }
-        }
-        else {
-            bug_message = "Get bug failed: " + data.responseText;
-        }
+    try {
+        const { bugs } = await Bugzilla.API.get(`bug/${bug_id}?`, {
+            include_fields: ['product', 'component', 'status', 'summary'],
+        });
+        const { product, component, status, summary } = bugs[0];
+        const bug_message = (product !== 'Mozilla Reps' || component !== 'Budget Requests') ?
+            "You can only attach budget payment information to bugs under \
+                the product 'Mozilla Reps' and component 'Budget Requests'." :
+            `Bug ${bug_id} - ${status} - ${summary}`;
+
         div.text(bug_message);
         bug_cache[bug_id] = bug_message;
-    }).fail(function(res, x, y) {
-        if (res.responseJSON && res.responseJSON.error) {
-            div.text(res.responseJSON.message);
-        }
-    });
+    } catch ({ message }) {
+        div.text(`Get bug failed: ${message}`);
+    }
+
     return true;
 }
 
index 20c4a296d9533d45f2f0892c852538a5b358af2a..d866db82dbf85af37986b630f740c56e80f761ee 100644 (file)
@@ -47,44 +47,7 @@ $(function () {
             return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);
         }
 
-        var flagDS, bugDS, attachmentDS, historyTable;
-        flagDS = new Y.DataSource.IO({ source: `${BUGZILLA.config.basepath}jsonrpc.cgi` });
-        flagDS.plug(Y.Plugin.DataSourceJSONSchema, {
-            schema: {
-                resultListLocator: 'result',
-                resultFields: [
-                    { key: 'id' },
-                    { key: 'requestee' },
-                    { key: 'setter' },
-                    { key: 'flag_id' },
-                    { key: 'creation_time' },
-                    { key: 'status' },
-                    { key: 'bug_id' },
-                    { key: 'type' },
-                    { key: 'attachment_id' }
-                ]
-            }
-        });
-
-        bugDS = new Y.DataSource.IO({ source: `${BUGZILLA.config.basepath}jsonrpc.cgi` });
-        bugDS.plug(Y.Plugin.DataSourceJSONSchema, {
-            schema: {
-                resultListLocator: 'result.bugs',
-                resultFields: [
-                    { key: 'id' },
-                    { key: 'summary' }
-                ]
-            }
-        });
-
-        attachmentDS = new Y.DataSource.IO({ source: `${BUGZILLA.config.basepath}jsonrpc.cgi` });
-        attachmentDS.plug(Y.Plugin.DataSourceJSONSchema, {
-            schema: {
-                metaFields: { 'attachments': 'result.attachments' }
-            }
-        });
-
-        historyTable = new Y.DataTable({
+        const historyTable = new Y.DataTable({
             columns: [
                 { key: 'creation_time', label: 'Created', sortable: true, formatter: format_date },
                 { key: 'attachment', label: 'Attachment', formatter: format_attachment },
@@ -97,159 +60,55 @@ $(function () {
             ]
         });
 
-        function fetch_flag_ids(user) {
-            return new Y.Promise(function (resolve, reject) {
-                var flagIdCallback = {
-                    success: function (e) {
-                        var flags = e.response.results;
-                        var flag_ids = flags.filter(function (flag) {
-                            return flag.status == '?';
-                        })
-                        .map(function (flag) {
-                            return flag.flag_id;
-                        });
-
-                        if (flag_ids.length > 0) {
-                            resolve(flag_ids);
-                        } else {
-                            reject("No reviews found");
-                        }
-                    },
-                    failure: function (e) {
-                        reject(e.error.message);
-                    }
-                };
-
-                flagDS.sendRequest({
-                    request: JSON.stringify({
-                        version: '1.1',
-                        method: 'Review.flag_activity',
-                        params: {
-                            type_name: 'review',
-                            requestee: user,
-                            include_fields: ['flag_id', 'status'],
-                            Bugzilla_api_token : (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-                        }
-                    }),
-                    cfg: {
-                        method: "POST",
-                        headers: { 'Content-Type': 'application/json' }
-                    },
-                    callback: flagIdCallback
-                });
-            });
-        }
+        const fetch_flags = async user => {
+            try {
+                const data = await Bugzilla.API.get('review/flag_activity', { type_name: 'review', requestee: user });
+                const flags = data.filter(flag => flag.status === '?');
 
-        function fetch_flags(flag_ids) {
-            return new Y.Promise(function (resolve, reject) {
-                flagDS.sendRequest({
-                    request: JSON.stringify({
-                        version: '1.1',
-                        method: 'Review.flag_activity',
-                        params: {
-                            flag_ids: flag_ids,
-                            Bugzilla_api_token: (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-                        }
-                    }),
-                    cfg: {
-                        method: 'POST',
-                        headers: { 'Content-Type': 'application/json' }
-                    },
-                    callback: {
-                        success: function (e) {
-                            var flags = e.response.results;
-                            flags.forEach(function(flag) {
-                                flag.creation_time = parse_date(flag.creation_time);
-                            });
-                            resolve(flags.sort(function (a, b) {
-                                if (a.id > b.id) return 1;
-                                if (a.id < b.id) return -1;
-                                return 0;
-                            }));
-                        },
-                        failure: function (e) {
-                            reject(e.error.message);
-                        }
-                    }
-                });
-            });
+                if (!flags.length) {
+                    return Promise.reject("No reviews found");
+                }
+
+                flags.forEach(flag => flag.creation_time = parse_date(flag.creation_time));
+                flags.sort((a, b) => a.id > b.id ? 1 : a.id < b.id ? -1 : 0);
+
+                return Promise.resolve(flags);
+            } catch ({ message }) {
+                return Promise.reject(message);
+            }
         }
 
-        function fetch_bug_summaries(flags) {
-            return new Y.Promise(function (resolve, reject) {
-                var bug_ids = Y.Array.dedupe(flags.map(function (f) {
-                    return f.bug_id;
-                }));
-
-                bugDS.sendRequest({
-                    request: JSON.stringify({
-                        version: '1.1',
-                        method: 'Bug.get',
-                        params: {
-                            ids: bug_ids,
-                            include_fields: ['summary', 'id'],
-                            Bugzilla_api_token: (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-                        }
-                    }),
-                    cfg: {
-                        method: 'POST',
-                        headers: { 'Content-Type': 'application/json' }
-                    },
-                    callback: {
-                        success: function (e) {
-                            var bugs    = e.response.results,
-                                summary = {};
-
-                            bugs.forEach(function (bug) {
-                                summary[bug.id] = bug.summary;
-                            });
-                            flags.forEach(function (flag) {
-                                flag.bug_summary = summary[flag.bug_id];
-                            });
-                            resolve(flags);
-                        },
-                        failure: function (e) {
-                            reject(e.error.message);
-                        }
-                    }
-                });
-            });
+        const fetch_bug_summaries = async flags => {
+            const bug_ids = [...(new Set(flags.map(flag => flag.bug_id)))]; // remove duplicates with `Set`
+            const summary = {};
+
+            try {
+                const { bugs } = await Bugzilla.API.get('bug', { id: bug_ids, include_fields: ['id', 'summary'] });
+
+                bugs.forEach(bug => summary[bug.id] = bug.summary);
+                flags.forEach(flag => flag.bug_summary = summary[flag.bug_id]);
+
+                return Promise.resolve(flags);
+            } catch ({ message }) {
+                return Promise.reject(message);
+            }
         }
 
-        function fetch_attachment_descriptions(flags) {
-            return new Y.Promise(function (resolve, reject) {
-                var attachment_ids = Y.Array.dedupe(flags.map(function (f) {
-                    return f.attachment_id;
-                }));
-
-                attachmentDS.sendRequest({
-                    request: JSON.stringify({
-                        version: '1.1',
-                        method: 'Bug.attachments',
-                        params: {
-                            attachment_ids: attachment_ids,
-                            include_fields: ['id', 'description'],
-                            Bugzilla_api_token : (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-                        }
-                    }),
-                    cfg: {
-                        method: 'POST',
-                        headers: { 'Content-Type': 'application/json' }
-                    },
-                    callback: {
-                        success: function (e) {
-                            var attachments = e.response.meta.attachments;
-                            flags.forEach(function (flag) {
-                                flag.attachment = attachments[flag.attachment_id];
-                            });
-                            resolve(flags);
-                        },
-                        failure: function (e) {
-                            reject(e.error.message);
-                        }
-                    }
+        const fetch_attachment_descriptions = async flags => {
+            const attachment_ids = [...(new Set(flags.map(flag => flag.attachment_id)))]; // remove duplicates
+
+            try {
+                const { attachments } = await Bugzilla.API.get(`bug/attachment/${attachment_ids[0]}`, {
+                    attachment_ids,
+                    include_fields: ['id', 'description'],
                 });
-            });
+
+                flags.forEach(flag => flag.attachment = attachments[flag.attachment_id]);
+
+                return Promise.resolve(flags);
+            } catch ({ message }) {
+                return Promise.reject(message);
+            }
         }
 
         function add_historical_action(history, flag, stash, action) {
@@ -359,12 +218,11 @@ $(function () {
             });
             historyTable.set('data', null);
             historyTable.showMessage('Loading...');
-            fetch_flag_ids(user)
-            .then(fetch_flags)
+            fetch_flags(user)
             .then(fetch_bug_summaries)
             .then(fetch_attachment_descriptions)
             .then(function (flags) {
-                return new Y.Promise(function (resolve, reject) {
+                return new Promise((resolve, reject) => {
                     try {
                         resolve(generate_history(flags, user));
                     }
@@ -385,9 +243,8 @@ $(function () {
 
     }, '0.0.1', {
         requires: [
-            "node", "datatype-date", "datatable", "datatable-sort", "datatable-message",
-            "datatable-datasource", "datasource-io", "datasource-jsonschema", "cookie",
-            "gallery-datatable-row-expansion-bmo", "handlebars", "promise"
+            'node', 'datatype-date', 'datatable', 'datatable-sort', 'datatable-message', 'cookie',
+            'gallery-datatable-row-expansion-bmo', 'handlebars'
         ]
     });
 });
index 480a578ae937c9cca4a9a9e424c228c48078d555..e716fdc8ed46b0457f46dfb2e9a09665fe8a4454 100644 (file)
@@ -88,7 +88,7 @@ $(function() {
     // mfa
 
     $('#mfa-select-totp')
-        .click(function(event) {
+        .click(async event => {
             event.preventDefault();
             $('#mfa').val('TOTP');
 
@@ -102,29 +102,19 @@ $(function() {
             $('#mfa-totp-throbber').show();
             $('#mfa-totp-issued').hide();
 
-            var url = `${BUGZILLA.config.basepath}rest/user/mfa/totp/enroll` +
-                `?Bugzilla_api_token=${encodeURIComponent(BUGZILLA.api_token)}`;
-            $.ajax({
-                "url": url,
-                "contentType": "application/json",
-                "processData": false
-            })
-            .done(function(data) {
+            try {
+                const { png, secret32 } = await Bugzilla.API.get('user/mfa/totp/enroll');
+
                 $('#mfa-totp-throbber').hide();
                 var iframe = $('#mfa-enable-totp-frame').contents();
-                iframe.find('#qr').attr('src', 'data:image/png;base64,' + data.png);
-                iframe.find('#secret').text(data.secret32);
+                iframe.find('#qr').attr('src', `data:image/png;base64,${png}`);
+                iframe.find('#secret').text(secret32);
                 $('#mfa-totp-issued').show();
                 $('#mfa-password').focus();
                 $('#update').attr('disabled', false);
-            })
-            .fail(function(data) {
+            } catch (ex) {
                 $('#mfa-totp-throbber').hide();
-                if (data.statusText === 'abort')
-                    return;
-                var message = data.responseJSON ? data.responseJSON.message : 'Unexpected Error';
-                console.log(message);
-            });
+            }
         });
 
     $('#mfa-select-duo')
index 209d034ad7a4f3b496e31d3582ddb9f48a08d3be..b453412fcfa754fb0b38f809403af5abfde0483a 100644 (file)
--- a/js/bug.js
+++ b/js/bug.js
    already. */
 
 YAHOO.bugzilla.dupTable = {
-    counter: 0,
-    dataSource: null,
-    updateTable: function(dataTable, product_name, summary_field) {
+    updateTable: async (dataTable, product_name, summary_field) => {
         if (summary_field.value.length < 4) return;
 
-        YAHOO.bugzilla.dupTable.counter = YAHOO.bugzilla.dupTable.counter + 1;
-        YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
-        var json_object = {
-            version : "1.1",
-            method : "Bug.possible_duplicates",
-            id : YAHOO.bugzilla.dupTable.counter,
-            params : {
-                Bugzilla_api_token: BUGZILLA.api_token,
-                product : product_name,
-                summary : summary_field.value,
-                limit : 7,
-                include_fields : [ "id", "summary", "status", "resolution",
-                                   "update_token" ]
-            }
-        };
-        var post_data = JSON.stringify(json_object);
-
-        var callback = {
-            success: dataTable.onDataReturnInitializeTable,
-            failure: dataTable.onDataReturnInitializeTable,
-            scope:   dataTable,
-            argument: dataTable.getState()
-        };
         dataTable.showTableMessage(dataTable.get("MSG_LOADING"),
                                    YAHOO.widget.DataTable.CLASS_LOADING);
         YAHOO.util.Dom.removeClass('possible_duplicates_container',
                                    'bz_default_hidden');
-        dataTable.getDataSource().sendRequest(post_data, callback);
+
+        let data = {};
+
+        try {
+            const { bugs } = await Bugzilla.API.get('bug/possible_duplicates', {
+                product: product_name,
+                summary: summary_field.value,
+                limit: 7,
+                include_fields: ['id', 'summary', 'status', 'resolution', 'update_token'],
+            });
+
+            data = { results: bugs };
+        } catch (ex) {
+            data = { error: true };
+        }
+
+        dataTable.onDataReturnInitializeTable('', data);
     },
     // This is the keyup event handler. It calls updateTable with a relatively
     // long delay, to allow additional input. However, the delay is short
@@ -98,108 +89,13 @@ YAHOO.bugzilla.dupTable = {
         el.appendChild(button);
         new YAHOO.widget.Button(button);
     },
-    init_ds: function() {
-        var new_ds = new YAHOO.util.XHRDataSource(`${BUGZILLA.config.basepath}jsonrpc.cgi`);
-        new_ds.connTimeout = 30000;
-        new_ds.connMethodPost = true;
-        new_ds.connXhrMode = "cancelStaleRequests";
-        new_ds.maxCacheEntries = 3;
-        new_ds.responseSchema = {
-            resultsList : "result.bugs",
-            metaFields : { error: "error", jsonRpcId: "id" }
-        };
-        // DataSource can't understand a JSON-RPC error response, so
-        // we have to modify the result data if we get one.
-        new_ds.doBeforeParseData =
-            function(oRequest, oFullResponse, oCallback) {
-                if (oFullResponse.error) {
-                    oFullResponse.result = {};
-                    oFullResponse.result.bugs = [];
-                    if (console) {
-                        console.log("JSON-RPC error:", oFullResponse.error);
-                    }
-                }
-                return oFullResponse;
-        }
-
-        this.dataSource = new_ds;
-    },
     init: function(data) {
-        if (this.dataSource == null) this.init_ds();
         data.options.initialLoad = false;
-        var dt = new YAHOO.widget.DataTable(data.container, data.columns,
-            this.dataSource, data.options);
+
+        const ds = new YAHOO.util.LocalDataSource([]); // Dummy data source
+        const dt = new YAHOO.widget.DataTable(data.container, data.columns, ds, data.options);
+
         YAHOO.util.Event.on(data.summary_field, 'input', this.doUpdateTable,
                             [dt, data.product_name]);
     }
 };
-
-(function(){
-    'use strict';
-
-    YAHOO.bugzilla.bugUserLastVisit = {
-        update: function(bug_ids) {
-            var args = JSON.stringify({
-                version: "1.1",
-                method: 'BugUserLastVisit.update',
-                params: {
-                    Bugzilla_api_token: BUGZILLA.api_token,
-                    ids: bug_ids
-                }
-            });
-            var callbacks = {
-                failure: function(res) {
-                    if (console)
-                        console.log("failed to update last visited: "
-                            + res.responseText);
-                },
-            };
-
-            YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
-            YAHOO.util.Connect.asyncRequest('POST', `${BUGZILLA.config.basepath}jsonrpc.cgi`, callbacks,
-                args)
-        },
-
-        get: function(done) {
-            var args = JSON.stringify({
-                version: "1.1",
-                method: 'BugUserLastVisit.get',
-                params: {
-                    Bugzilla_api_token: BUGZILLA.api_token
-                }
-            });
-            var callbacks = {
-                success: function(res) { done(JSON.parse(res.responseText)) },
-                failure: function(res) {
-                    if (console)
-                        console.log("failed to get last visited: "
-                                + res.responseText);
-                },
-            };
-            YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
-            YAHOO.util.Connect.asyncRequest('POST', `${BUGZILLA.config.basepath}jsonrpc.cgi`, callbacks, args)
-        },
-    };
-
-    YAHOO.bugzilla.bugInterest = {
-        unmark: function(bug_ids) {
-            var args = JSON.stringify({
-                version: "1.1",
-                method: 'MyDashboard.bug_interest_unmark',
-                params: {
-                    bug_ids: bug_ids,
-                    Bugzilla_api_token: (BUGZILLA.api_token ? BUGZILLA.api_token : '')
-                },
-            });
-            var callbacks = {
-                failure: function(res) {
-                    if (console)
-                        console.log("failed to unmark interest: "
-                            + res.responseText);
-                },
-            };
-            YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
-            YAHOO.util.Connect.asyncRequest('POST', `${BUGZILLA.config.basepath}jsonrpc.cgi`, callbacks, args)
-        },
-    };
-})();
index 3f19f87c0edbe01154a4bd73cef9e10fe83a8073..fb7bdb61ec851a871ba5a03fc88fed60289f0f85 100644 (file)
@@ -37,25 +37,15 @@ YAHOO.bugzilla.commentTagging = {
         $('#bz_ctag_add').devbridgeAutocomplete({
             appendTo: $('#main-inner'),
             forceFixPosition: true,
-            serviceUrl: function(query) {
-                return `${BUGZILLA.config.basepath}rest/bug/comment/tags/${encodeURIComponent(query)}`;
-            },
-            params: {
-                Bugzilla_api_token: BUGZILLA.api_token
-            },
             deferRequestBy: 250,
             minChars: 1,
             tabDisabled: true,
-            transformResult: function(response) {
-                response = $.parseJSON(response);
-                return {
-                    suggestions: $.map(response, function(dataItem) {
-                        return {
-                            value: dataItem,
-                            data : null
-                        };
-                    })
-                };
+            lookup: (query, done) => {
+                // Note: `async` doesn't work for this `lookup` function, so use a `Promise` chain instead
+                Bugzilla.API.get(`bug/comment/tags/${encodeURIComponent(query)}`)
+                    .then(data => data.map(tag => ({ value: tag })))
+                    .catch(() => [])
+                    .then(suggestions => done({ suggestions }));
             }
         });
     },
@@ -74,7 +64,7 @@ YAHOO.bugzilla.commentTagging = {
 
         } else {
             // show or move
-            this.rpcRefresh(comment_id, comment_no);
+            this.fetchRefresh(comment_id, comment_no);
             this.current_id = comment_id;
             this.current_no = comment_no;
             this.ctag_add.value = '';
@@ -274,14 +264,14 @@ YAHOO.bugzilla.commentTagging = {
         tags.sort();
         // update
         this.showTags(comment_id, comment_no, tags);
-        this.rpcUpdate(comment_id, comment_no, new_tags, undefined);
+        this.fetchUpdate(comment_id, comment_no, new_tags, undefined);
     },
 
     remove : function(comment_id, comment_no, tag) {
         var el = Dom.get('ct_' + comment_no + '_' + tag);
         if (el) {
             el.parentNode.removeChild(el);
-            this.rpcUpdate(comment_id, comment_no, undefined, [ tag ]);
+            this.fetchUpdate(comment_id, comment_no, undefined, [ tag ]);
             this.hideEmpty(comment_no);
         }
     },
@@ -306,80 +296,49 @@ YAHOO.bugzilla.commentTagging = {
                && this.pending['c' + comment_id] > 0;
     },
 
-    rpcRefresh : function(comment_id, comment_no, noRefreshOnError) {
-        this.incPending(comment_id);
-        YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
-        YAHOO.util.Connect.asyncRequest('POST', `${BUGZILLA.config.basepath}jsonrpc.cgi`,
-        {
-            success: function(res) {
-                YAHOO.bugzilla.commentTagging.decPending(comment_id);
-                data = JSON.parse(res.responseText);
-                if (data.error) {
-                    YAHOO.bugzilla.commentTagging.handleRpcError(
-                        comment_id, comment_no, data.error.message, noRefreshOnError);
-                    return;
-                }
+    fetchRefresh : async (comment_id, comment_no, noRefreshOnError) => {
+        const self = YAHOO.bugzilla.commentTagging;
 
-                if (!YAHOO.bugzilla.commentTagging.hasPending(comment_id))
-                    YAHOO.bugzilla.commentTagging.showTags(
-                        comment_id, comment_no, data.result.comments[comment_id].tags);
-            },
-            failure: function(res) {
-                YAHOO.bugzilla.commentTagging.decPending(comment_id);
-                YAHOO.bugzilla.commentTagging.handleRpcError(
-                    comment_id, comment_no, res.responseText, noRefreshOnError);
-            }
-        },
-        JSON.stringify({
-            version: "1.1",
-            method: 'Bug.comments',
-            params: {
-                Bugzilla_api_token: BUGZILLA.api_token,
-                comment_ids: [ comment_id ],
-                include_fields: [ 'tags' ]
+        self.incPending(comment_id);
+
+        try {
+            const { comments } = await Bugzilla.API.get(`bug/comment/${comment_id}?include_fields=tags`);
+
+            self.decPending(comment_id);
+
+            if (!self.hasPending(comment_id)) {
+                self.showTags(comment_id, comment_no, comments[comment_id].tags);
             }
-        })
-        );
+        } catch ({ message }) {
+            self.decPending(comment_id);
+            self.handleFetchError(comment_id, comment_no, message, noRefreshOnError);
+        }
     },
 
-    rpcUpdate : function(comment_id, comment_no, add, remove) {
-        this.incPending(comment_id);
-        YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
-        YAHOO.util.Connect.asyncRequest('POST', `${BUGZILLA.config.basepath}jsonrpc.cgi`,
-        {
-            success: function(res) {
-                YAHOO.bugzilla.commentTagging.decPending(comment_id);
-                data = JSON.parse(res.responseText);
-                if (data.error) {
-                    YAHOO.bugzilla.commentTagging.handleRpcError(comment_id, comment_no, data.error.message);
-                    return;
-                }
+    fetchUpdate : async (comment_id, comment_no, add, remove) => {
+        const self = YAHOO.bugzilla.commentTagging;
 
-                if (!YAHOO.bugzilla.commentTagging.hasPending(comment_id))
-                    YAHOO.bugzilla.commentTagging.showTags(comment_id, comment_no, data.result);
-            },
-            failure: function(res) {
-                YAHOO.bugzilla.commentTagging.decPending(comment_id);
-                YAHOO.bugzilla.commentTagging.handleRpcError(comment_id, comment_no, res.responseText);
-            }
-        },
-        JSON.stringify({
-            version: "1.1",
-            method: 'Bug.update_comment_tags',
-            params: {
-                Bugzilla_api_token: BUGZILLA.api_token,
-                comment_id: comment_id,
-                add: add,
-                remove: remove
+        self.incPending(comment_id);
+
+        try {
+            const data = await Bugzilla.API.put(`bug/comment/${comment_id}/tags`, { comment_id, add, remove });
+
+            if (!self.hasPending(comment_id)) {
+                self.showTags(comment_id, comment_no, data);
             }
-        })
-        );
+        } catch ({ message }) {
+            self.decPending(comment_id);
+            self.handleFetchError(comment_id, comment_no, message);
+        }
     },
 
-    handleRpcError : function(comment_id, comment_no, message, noRefreshOnError) {
-        YAHOO.bugzilla.commentTagging.showError(comment_id, comment_no, message);
+    handleFetchError : (comment_id, comment_no, message, noRefreshOnError) => {
+        const self = YAHOO.bugzilla.commentTagging;
+
+        self.showError(comment_id, comment_no, message);
+
         if (!noRefreshOnError) {
-            YAHOO.bugzilla.commentTagging.rpcRefresh(comment_id, comment_no, true);
+            self.fetchRefresh(comment_id, comment_no, true);
         }
     }
 }
index da1c44b9ae7f9b772d77651205bf56351df6a738..a3680cdf72ca1b950335c156652f1d8b1dea01e7 100644 (file)
@@ -717,10 +717,6 @@ $(function() {
     var options_user = {
         appendTo: $('#main-inner'),
         forceFixPosition: true,
-        serviceUrl: `${BUGZILLA.config.basepath}rest/user/suggest`,
-        params: {
-            Bugzilla_api_token: BUGZILLA.api_token,
-        },
         paramName: 'match',
         deferRequestBy: 250,
         minChars: 2,
@@ -729,16 +725,15 @@ $(function() {
         autoSelectFirst: true,
         preserveInput: true,
         triggerSelectOnValidInput: false,
-        transformResult: function(response) {
-            response = $.parseJSON(response);
-            return {
-                suggestions: $.map(response.users, function({ name, real_name, requests, gravatar } = {}) {
-                    return {
-                        value: name,
-                        data : { email: name, real_name, requests, gravatar }
-                    };
-                })
-            };
+        lookup: (query, done) => {
+            // Note: `async` doesn't work for this `lookup` function, so use a `Promise` chain instead
+            Bugzilla.API.get('user/suggest', { match: query })
+                .then(({ users }) => users.map(({ name, real_name, requests, gravatar }) => ({
+                    value: name,
+                    data: { email: name, real_name, requests, gravatar },
+                })))
+                .catch(() => [])
+                .then(suggestions => done({ suggestions }));
         },
         formatResult: function(suggestion) {
             const $input = this;
@@ -921,7 +916,7 @@ function initDirtyFieldTracking() {
 
 var last_comment_text = '';
 
-function show_comment_preview(bug_id) {
+async function show_comment_preview(bug_id) {
     var Dom = YAHOO.util.Dom;
     var comment = document.getElementById('comment');
     var preview = document.getElementById('comment_preview');
@@ -951,46 +946,24 @@ function show_comment_preview(bug_id) {
     Dom.addClass('comment_preview_text', 'bz_default_hidden');
     Dom.removeClass('comment_preview_loading', 'bz_default_hidden');
 
-    YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
-    YAHOO.util.Connect.asyncRequest('POST', `${BUGZILLA.config.basepath}jsonrpc.cgi`,
-    {
-        success: function(res) {
-            data = JSON.parse(res.responseText);
-            if (data.error) {
-                Dom.addClass('comment_preview_loading', 'bz_default_hidden');
-                Dom.removeClass('comment_preview_error', 'bz_default_hidden');
-                Dom.get('comment_preview_error').innerHTML =
-                    data.error.message.htmlEncode();
-            } else {
-                $comment_body.innerHTML = data.result.html;
+    try {
+        const { html } = await Bugzilla.API.post('bug/comment/render', { id: bug_id, text: comment.value });
 
-                // Highlight code if possible
-                if (Prism) {
-                  Prism.highlightAllUnder($comment_body);
-                }
+        $comment_body.innerHTML = html;
 
-                Dom.addClass('comment_preview_loading', 'bz_default_hidden');
-                Dom.removeClass('comment_preview_text', 'bz_default_hidden');
-                last_comment_text = comment.value;
-            }
-        },
-        failure: function(res) {
-            Dom.addClass('comment_preview_loading', 'bz_default_hidden');
-            Dom.removeClass('comment_preview_error', 'bz_default_hidden');
-            Dom.get('comment_preview_error').innerHTML =
-                res.responseText.htmlEncode();
-        }
-    },
-    JSON.stringify({
-        version: "1.1",
-        method: 'Bug.render_comment',
-        params: {
-            Bugzilla_api_token: BUGZILLA.api_token,
-            id: bug_id,
-            text: comment.value
+        // Highlight code if possible
+        if (Prism) {
+            Prism.highlightAllUnder($comment_body);
         }
-    })
-    );
+
+        Dom.addClass('comment_preview_loading', 'bz_default_hidden');
+        Dom.removeClass('comment_preview_text', 'bz_default_hidden');
+        last_comment_text = comment.value;
+    } catch ({ message }) {
+        Dom.addClass('comment_preview_loading', 'bz_default_hidden');
+        Dom.removeClass('comment_preview_error', 'bz_default_hidden');
+        Dom.get('comment_preview_error').innerHTML = YAHOO.lang.escapeHTML(message);
+    }
 }
 
 function show_comment_edit() {
index 11510043d4119ed99e57dd991224f7bd32520eac..1c71f3f05c54bc6b38d43dfa0054a6fd518593cf 100644 (file)
@@ -155,49 +155,6 @@ function display_value(field, value) {
     return value;
 }
 
-// ajax wrapper, to simplify error handling and auth
-// TODO: Rewrite this method using Promise (Bug 1380437)
-function bugzilla_ajax(request, done_fn, error_fn) {
-    $('#xhr-error').hide('');
-    $('#xhr-error').html('');
-    if (BUGZILLA.api_token) {
-        request.url += (request.url.match('\\?') ? '&' : '?') +
-            'Bugzilla_api_token=' + encodeURIComponent(BUGZILLA.api_token);
-    }
-    if (request.type != 'GET') {
-        request.contentType = 'application/json';
-        request.processData = false;
-        if (request.data && request.data.constructor === Object) {
-            request.data = JSON.stringify(request.data);
-        }
-    }
-    return $.ajax(request)
-        .done(function(data) {
-            if (data.error) {
-                if (!request.hideError) {
-                    $('#xhr-error').html(data.message);
-                    $('#xhr-error').show('fast');
-                }
-                if (error_fn)
-                    error_fn(data.message);
-            }
-            else if (done_fn) {
-                done_fn(data);
-            }
-        })
-        .fail(function(data) {
-            if (data.statusText === 'abort')
-                return;
-            var message = data.responseJSON ? data.responseJSON.message : 'Unexpected Error'; // all errors are unexpected :)
-            if (!request.hideError) {
-                $('#xhr-error').html(message);
-                $('#xhr-error').show('fast');
-            }
-            if (error_fn)
-                error_fn(message);
-        });
-}
-
 // html encoding
 if (!String.prototype.htmlEncode) {
     (function() {
index 23d53680c59a0d6fc5e6b4a557264e4999167654..742fe096d81f7bf103f8bc2b4289824479622565 100644 (file)
@@ -18,7 +18,6 @@ Event.onDOMReady(function() {
 });
 
 YAHOO.bugzilla.instantSearch = {
-  counter: 0,
   dataTable: null,
   dataTableColumns: null,
   elContent: null,
@@ -27,8 +26,6 @@ YAHOO.bugzilla.instantSearch = {
   currentSearchProduct: '',
 
   onInit: function() {
-    YAHOO.util.Connect.setDefaultPostHeader('application/json; charset=UTF-8');
-
     this.elContent = Dom.get('content');
     this.elList = Dom.get('results');
 
@@ -46,37 +43,10 @@ YAHOO.bugzilla.instantSearch = {
   },
 
   initDataTable: function() {
-    var dataSource = new YAHOO.util.XHRDataSource(`${BUGZILLA.config.basepath}jsonrpc.cgi`);
-    dataSource.connTimeout = 15000;
-    dataSource.connMethodPost = true;
-    dataSource.connXhrMode = "cancelStaleRequests";
-    dataSource.maxCacheEntries = 3;
-    dataSource.responseSchema = {
-      resultsList : "result.bugs",
-      metaFields : { error: "error", jsonRpcId: "id" }
-    };
-    // DataSource can't understand a JSON-RPC error response, so
-    // we have to modify the result data if we get one.
-    dataSource.doBeforeParseData =
-      function(oRequest, oFullResponse, oCallback) {
-        if (oFullResponse.error) {
-          oFullResponse.result = {};
-          oFullResponse.result.bugs = [];
-          if (console)
-            console.error("JSON-RPC error:", oFullResponse.error);
-        }
-        return oFullResponse;
-      };
-    dataSource.subscribe('dataErrorEvent',
-      function() {
-        YAHOO.bugzilla.instantSearch.currentSearchQuery = '';
-      }
-    );
-
     this.dataTable = new YAHOO.widget.DataTable(
       'results',
       this.dataTableColumns,
-      dataSource,
+      new YAHOO.util.LocalDataSource([]), // Dummy data source
       {
         initialLoad: false,
         MSG_EMPTY: 'No matching bugs found.',
@@ -117,7 +87,7 @@ YAHOO.bugzilla.instantSearch = {
     YAHOO.bugzilla.instantSearch.doSearch(YAHOO.bugzilla.instantSearch.getContent());
   },
 
-  doSearch: function(query) {
+  doSearch: async query => {
     if (query.length < 4)
       return;
 
@@ -142,41 +112,30 @@ YAHOO.bugzilla.instantSearch = {
         ' width="16" height="11">',
         YAHOO.widget.DataTable.CLASS_LOADING
       );
-      var jsonObject = {
-        version: "1.1",
-        method: "Bug.possible_duplicates",
-        id: ++YAHOO.bugzilla.instantSearch.counter,
-        params: {
+
+      let data;
+
+      try {
+        const { bugs } = await Bugzilla.API.get('bug/possible_duplicates', {
           product: YAHOO.bugzilla.instantSearch.getProduct(),
           summary: query,
           limit: 20,
-          include_fields: [ "id", "summary", "status", "resolution", "component" ]
-        }
-      };
-      if (BUGZILLA.api_token) {
-        jsonObject.params.Bugzilla_api_token = BUGZILLA.api_token;
-      }
+          include_fields: ['id', 'summary', 'status', 'resolution', 'component'],
+        });
 
-      YAHOO.bugzilla.instantSearch.dataTable.getDataSource().sendRequest(
-        JSON.stringify(jsonObject),
-        {
-          success: YAHOO.bugzilla.instantSearch.onSearchResults,
-          failure: YAHOO.bugzilla.instantSearch.onSearchResults,
-          scope: YAHOO.bugzilla.instantSearch.dataTable,
-          argument: YAHOO.bugzilla.instantSearch.dataTable.getState()
-        }
-      );
+        data = { results: bugs };
+      } catch (ex) {
+        YAHOO.bugzilla.instantSearch.currentSearchQuery = '';
+        data = { error: true };
+      }
 
+      YAHOO.bugzilla.instantSearch.dataTable.onDataReturnInitializeTable('', data);
     } catch(err) {
       if (console)
         console.error(err.message);
     }
   },
 
-  onSearchResults: function(sRequest, oResponse, oPayload) {
-    YAHOO.bugzilla.instantSearch.dataTable.onDataReturnInitializeTable(sRequest, oResponse, oPayload);
-  },
-
   getContent: function() {
     var content = this.elContent.value.trim();
     // work around chrome bug
index 83ba3b3c59ae1b757054afaf1feaceca60d55232..5f937444876a36f6885153085b8df8ddc3ad006f 100644 (file)
@@ -368,3 +368,283 @@ function timeAgo(param) {
     if (mo < 18) return 'Last year';
     return yy + ' years ago';
 }
+
+/**
+ * Reference or define the Bugzilla app namespace.
+ * @namespace
+ */
+var Bugzilla = Bugzilla || {}; // eslint-disable-line no-var
+
+/**
+ * Enable easier access to the Bugzilla REST API.
+ * @hideconstructor
+ * @see https://bugzilla.readthedocs.io/en/latest/api/
+ */
+Bugzilla.API = class API {
+  /**
+   * Initialize the request settings for `fetch()` or `XMLHttpRequest`.
+   * @private
+   * @param {String} endpoint See the {@link Bugzilla.API._fetch} method.
+   * @param {String} [method='GET'] See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [params] See the {@link Bugzilla.API._fetch} method.
+   * @returns {Request} Request settings including the complete URL, HTTP method, headers and body.
+   */
+  static _init(endpoint, method = 'GET', params = {}) {
+    const url = new URL(`${BUGZILLA.config.basepath}rest/${endpoint}`, location.origin);
+
+    if (method === 'GET') {
+      for (const [key, value] of Object.entries(params)) {
+        if (Array.isArray(value)) {
+          if (['include_fields', 'exclude_fields'].includes(key)) {
+            url.searchParams.set(key, value.join(','));
+          } else {
+            // Because the REST API v1 doesn't support comma-separated values for certain params, array values have to
+            // be appended as duplicated params, so the query string will look like `attachment_ids=1&attachment_ids=2`
+            // instead of `attachment_ids=1,2`.
+            value.forEach(val => url.searchParams.append(key, val));
+          }
+        } else {
+          url.searchParams.set(key, value);
+        }
+      }
+    }
+
+    /** @todo Remove this once Bug 1477163 is solved */
+    url.searchParams.set('Bugzilla_api_token', BUGZILLA.api_token);
+
+    return new Request(url, {
+      method,
+      body: method !== 'GET' ? JSON.stringify(params) : null,
+      credentials: 'same-origin',
+      cache: 'no-cache',
+    });
+  }
+
+  /**
+   * Send a `fetch()` request to a Bugzilla REST API endpoint and return results.
+   * @private
+   * @param {String} endpoint URL path excluding the `/rest/` prefix; may also contain query parameters.
+   * @param {Object} [options] Request options.
+   * @param {String} [options.method='GET'] HTTP request method. POST, GET, PUT, etc.
+   * @param {Object} [options.params] Request parameters. For a GET request, it will be sent as the URL query params.
+   * The values will be automatically URL-encoded, so don't use `encodeURIComponent()` for each. For a non-GET request,
+   * this will be part of the request body. The params can be included in `endpoint` if those are simple (no encoding
+   * required) or if the request is not GET but URL query params are required.
+   * @param {Object} [options.init] Extra options for the `fetch()` method.
+   * @returns {Promise.<(Object|Array.<Object>|Error)>} Response data for a valid response, or an `Error` object for an
+   * error returned from the REST API as well as any other exception including an aborted or failed HTTP request.
+   * @see https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
+   */
+  static async _fetch(endpoint, { method = 'GET', params = {}, init = {} } = {}) {
+    const request = this._init(endpoint, method, params);
+
+    if (!navigator.onLine) {
+      return Promise.reject(new Bugzilla.Error({ name: 'OfflineError', message: 'You are currently offline.' }));
+    }
+
+    return new Promise((resolve, reject) => {
+      const timer = window.setTimeout(() => {
+        reject(new Bugzilla.Error({ name: 'TimeoutError', message: 'Request Timeout' }));
+      }, 30000);
+
+      /** @throws {AbortError} */
+      fetch(request, init).then(response => {
+        /** @throws {SyntaxError} */
+        return response.ok ? response.json() : { error: true };
+      }).then(result => {
+        const { error, code, message } = result;
+
+        if (error) {
+          reject(new Bugzilla.Error({ name: 'APIError', code, message }));
+        } else {
+          resolve(result);
+        }
+      }).catch(({ name, code, message }) => {
+        reject(new Bugzilla.Error({ name, code, detail: message }));
+      });
+
+      window.clearTimeout(timer);
+    });
+  }
+
+  /**
+   * Shorthand for a GET request with the {@link Bugzilla.API._fetch} method.
+   * @param {String} endpoint See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [params] See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [init] See the {@link Bugzilla.API._fetch} method.
+   * @returns {Promise.<(Object|Array.<Object>|Error)>} See the {@link Bugzilla.API._fetch} method.
+   */
+  static async get(endpoint, params = {}, init = {}) {
+    return this._fetch(endpoint, { method: 'GET', params, init });
+  }
+
+  /**
+   * Shorthand for a POST request with the {@link Bugzilla.API._fetch} method.
+   * @param {String} endpoint See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [params] See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [init] See the {@link Bugzilla.API._fetch} method.
+   * @returns {Promise.<(Object|Array.<Object>|Error)>} See the {@link Bugzilla.API._fetch} method.
+   */
+  static async post(endpoint, params = {}, init = {}) {
+    return this._fetch(endpoint, { method: 'POST', params, init });
+  }
+
+  /**
+   * Shorthand for a PUT request with the {@link Bugzilla.API._fetch} method.
+   * @param {String} endpoint See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [params] See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [init] See the {@link Bugzilla.API._fetch} method.
+   * @returns {Promise.<(Object|Array.<Object>|Error)>} See the {@link Bugzilla.API._fetch} method.
+   */
+  static async put(endpoint, params = {}, init = {}) {
+    return this._fetch(endpoint, { method: 'PUT', params, init });
+  }
+
+  /**
+   * Shorthand for a PATCH request with the {@link Bugzilla.API._fetch} method.
+   * @param {String} endpoint See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [params] See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [init] See the {@link Bugzilla.API._fetch} method.
+   * @returns {Promise.<(Object|Array.<Object>|Error)>} See the {@link Bugzilla.API._fetch} method.
+   */
+  static async patch(endpoint, params = {}, init = {}) {
+    return this._fetch(endpoint, { method: 'PATCH', params, init });
+  }
+
+  /**
+   * Shorthand for a DELETE request with the {@link Bugzilla.API._fetch} method.
+   * @param {String} endpoint See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [params] See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [init] See the {@link Bugzilla.API._fetch} method.
+   * @returns {Promise.<(Object|Array.<Object>|Error)>} See the {@link Bugzilla.API._fetch} method.
+   */
+  static async delete(endpoint, params = {}, init = {}) {
+    return this._fetch(endpoint, { method: 'DELETE', params, init });
+  }
+
+  /**
+   * Success callback function for the {@link Bugzilla.API.xhr} method.
+   * @callback Bugzilla.API~resolve
+   * @param {Object|Array.<Object>} data Response data for a valid response.
+   */
+
+  /**
+   * Error callback function for the {@link Bugzilla.API.xhr} method.
+   * @callback Bugzilla.API~reject
+   * @param {Error} error `Error` object providing a reason. See the {@link Bugzilla.Error} for details.
+   */
+
+  /**
+   * Make an `XMLHttpRequest` to a Bugzilla REST API endpoint and return results. This is useful when you need more
+   * control than `fetch()`, particularly to upload a file while monitoring the progress.
+   * @param {String} endpoint See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [options] Request options.
+   * @param {String} [options.method='GET'] See the {@link Bugzilla.API._fetch} method.
+   * @param {Object} [options.params] See the {@link Bugzilla.API._fetch} method.
+   * @param {Bugzilla.API~resolve} [options.resolve] Callback function for a valid response.
+   * @param {Bugzilla.API~reject} [options.reject] Callback function for an error returned from the REST API as well as
+   * any other exception including an aborted or failed HTTP request.
+   * @param {Object.<String, Function>} [options.download] Raw event listeners for download; the key is an event type
+   * such as `load` or `error`, the value is an event listener.
+   * @param {Object.<String, Function>} [options.upload] Raw event listeners for upload; the key is an event type such
+   * as `progress` or `error`, the value is an event listener.
+   * @returns {XMLHttpRequest} Request.
+   * @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Monitoring_progress
+   */
+  static xhr(endpoint, { method = 'GET', params = {}, resolve, reject, download, upload } = {}) {
+    const xhr = new XMLHttpRequest();
+    const { url, headers, body } = this._init(endpoint, method, params);
+
+    resolve = typeof resolve === 'function' ? resolve : () => {};
+    reject = typeof reject === 'function' ? reject : () => {};
+
+    if (!navigator.onLine) {
+      reject(new Bugzilla.Error({ name: 'OfflineError', message: 'You are currently offline.' }));
+
+      return xhr;
+    }
+
+    xhr.addEventListener('load', () => {
+      try {
+        /** @throws {SyntaxError} */
+        const result = JSON.parse(xhr.responseText);
+        const { error, code, message } = result;
+
+        if (parseInt(xhr.status / 100) !== 2 || error) {
+          reject(new Bugzilla.Error({ name: 'APIError', code, message }));
+        } else {
+          resolve(result);
+        }
+      } catch ({ name, code, message }) {
+        reject(new Bugzilla.Error({ name, code, detail: message }));
+      }
+    });
+
+    xhr.addEventListener('abort', () => {
+      reject(new Bugzilla.Error({ name: 'AbortError' }));
+    });
+
+    xhr.addEventListener('error', () => {
+      reject(new Bugzilla.Error({ name: 'NetworkError' }));
+    });
+
+    xhr.addEventListener('timeout', () => {
+      reject(new Bugzilla.Error({ name: 'TimeoutError', message: 'Request Timeout' }));
+    });
+
+    for (const [type, handler] of Object.entries(download || {})) {
+      xhr.addEventListener(type, event => handler(event));
+    }
+
+    for (const [type, handler] of Object.entries(upload || {})) {
+      xhr.upload.addEventListener(type, event => handler(event));
+    }
+
+    xhr.open(method, url);
+
+    for (const [key, value] of headers) {
+      xhr.setRequestHeader(key, value);
+    }
+
+    // Set timeout in 30 seconds given some large results
+    xhr.timeout = 30000;
+
+    xhr.send(body);
+
+    return xhr;
+  }
+};
+
+/**
+ * Extend the generic `Error` class so it can contain a custom name and other data if needed. This allows to gracefully
+ * handle an error from either the REST API or the browser.
+ */
+Bugzilla.Error = class CustomError extends Error {
+  /**
+   * Initialize the `CustomError` object.
+   * @param {Object} [options] Error options.
+   * @param {String} [options.name='Error'] Distinguishable error name that can be taken from an original `DOMException`
+   * object if any, e.g. `AbortError` or `SyntaxError`.
+   * @param {String} [options.message='Unexpected Error'] Localizable, user-friendly message probably from the REST API.
+   * @param {Number} [options.code=0] Custom error code from the REST API or `DOMException` code.
+   * @param {String} [options.detail] Detailed, technical message probably from an original `DOMException` that end
+   * users don't have to see.
+   */
+  constructor({ name = 'Error', message = 'Unexpected Error', code = 0, detail } = {}) {
+    super(message);
+    this.name = name;
+    this.code = code;
+    this.detail = detail;
+
+    console.error(this.toString());
+  }
+
+  /**
+   * Define the string representation of the error object.
+   * @override
+   * @returns {String} Custom string representation.
+   */
+  toString() {
+    return `${this.name}: "${this.message}" (code: ${this.code}${this.detail ? `, detail: ${this.detail}` : ''})`;
+  }
+};
index d6d00102a6edfdd8dd68e541ec3c1704a17ccc1a..916601e7aab0112b1b2d372152a7d166dd62d9cf 100644 (file)
@@ -68,7 +68,7 @@
       initDirtyFieldTracking();
 
       [% IF user.id %]
-        YAHOO.bugzilla.bugUserLastVisit.update([ [% bug.bug_id FILTER none %] ]);
+        Bugzilla.API.post('bug_user_last_visit/[% bug.bug_id FILTER none %]');
       [% END %]
     });
     BUGZILLA.bug_id = [% bug.id FILTER none %];
index dc98cae5904cbc5f96f976bfafa7fe021dd8bc53..b42d87d22487cc027bb58b95be8ed07baf360780 100644 (file)
@@ -54,7 +54,7 @@
   no_yui = 0
   jquery = []
   jquery_css = []
-  generate_api_token = 0
+  generate_api_token = 1
   robots = 'index'
 %]