js/details.js (789 lines of code) (raw):

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ var NeedInfoConfig = null; var bugset = []; // buttons that get enabled only when there is an api key saved. var buttons = ['button-clear', 'button-clearcmt', 'button-redir', 'button-redirsetter']; //'button-redirassignee' // list of bugs we are submitting changes for. var ChangeListSize = 0; var ChangeList = []; // Testing the progress. Set to true to simulate submitting bug changes. var ChangeListTest = false; var TestDelay; $(document).ready(function () { loadList(); }); function loadList() { let jsonUrl = 'js/config.json'; $.getJSON(jsonUrl, function (configdata) { main(configdata); }).fail(function (jqXHR, textStatus, errorThrown) { console.log("getJSON call failed for some reason.", jsonUrl, errorThrown) }); } function getBugzillaMaxDateQuery() { // query date if it exists let queryDate = getQueryDate(); // console.log('date', date); // '2024-05-08' | bugzilla: '2014-09-29T14:25:35Z' if (queryDate && queryDate.length) { // chfieldfrom=2023-05-23&chfield=[Bug creation] return '&chfield=[Bug creation]' + '&chfieldfrom=' + queryDate; } return ''; } function main(json) { NeedInfoConfig = json.bugzillaconfig; NeedInfoConfig.developers = {}; updateDomains(); // user bugzilla id let userId = getUserId(); if (userId == undefined) { console.log("missing user id url parameter.") return; } // odr, cdr, onb, cnb let userQuery = getUserQuery(); if (userQuery == undefined) { console.log("missing user query url parameter.") return; } loadSettingsInternal(); updateButtonsState(); prepPage(userQuery); let id = encodeURIComponent(getUserId()); ////////////////////////////////////////// // Base query and resulting fields request ////////////////////////////////////////// // v1={id} // o1=equals // f1=requestees.login_name // f2=flagtypes.name // o2=equals // v2=needinfo? let url = NeedInfoConfig.bugzilla_search_url; if (NeedInfoConfig.api_key.length > 0) { url += "api_key=" + NeedInfoConfig.api_key + "&"; } url += NeedInfoConfig.bugs_query.replace("{id}", id); url += getBugzillaMaxDateQuery(); switch (userQuery) { ////////////////////////////////////////// // Open Developer Related ////////////////////////////////////////// case 'odr': url += "&f3=setters.login_name"; url += "&o3=nowordssubstr"; url += "&v3=release-mgmt-account-bot%40mozilla.tld"; // Ignore needinfos set by the account we're querying for. if (NeedInfoConfig.ignoremyni) { url += "," + id; } url += "&f4=bug_status"; url += "&o4=nowordssubstr"; url += "&v4=RESOLVED%2CVERIFIED%2CCLOSED"; break; ///////////////////////////////////////////////////////// // Open and Tracked // Query uses the generic tag names that bugzilla maps // to the current version tags. ///////////////////////////////////////////////////////// case 'otr': // f4=bug_status // o4=nowordssubstr / anywordssubstr // v4=RESOLVED%2CVERIFIED%2CCLOSED url += "&f3=bug_status"; url += "&o3=nowordssubstr"; url += "&v3=RESOLVED%2CVERIFIED%2CCLOSED"; // f5=cf_tracking_firefox_beta // o5=anywords // v5=%2B url += '&f4=OP'; url += '&j4=OR'; url += "&f5=cf_tracking_firefox_nightly"; url += "&o5=anywords"; url += "&v5=%2B"; url += "&f6=cf_tracking_firefox_beta"; url += "&o6=anywords"; url += "&v6=%2B"; url += "&f7=cf_tracking_firefox_release"; url += "&o7=anywords"; url += "&v7=%2B"; url += '&f8=CP' break; ////////////////////////////////////////// // Closed Developer Related ////////////////////////////////////////// case 'cdr': url += "&f3=setters.login_name"; url += "&o3=notequals"; url += "&v3=release-mgmt-account-bot%40mozilla.tld"; url += "&f4=bug_status"; url += "&o4=anywordssubstr"; url += "&v4=RESOLVED%2CVERIFIED%2CCLOSED"; // Ignore needinfos set by the account we're querying for. if (NeedInfoConfig.ignoremyni) { url += "&f5=setters.login_name"; url += "&o5=notequals"; url += "&v5=" + id; } break; ////////////////////////////////////////// // Open Nagbot ////////////////////////////////////////// case 'onb': url += "&f3=setters.login_name"; url += "&o3=equals"; url += "&v3=release-mgmt-account-bot%40mozilla.tld"; url += "&f4=bug_status"; url += "&o4=nowordssubstr"; url += "&v4=RESOLVED%2CVERIFIED%2CCLOSED"; break; ////////////////////////////////////////// // Closed Nagbot ////////////////////////////////////////// case 'cnb': url += "&f3=setters.login_name"; url += "&o3=equals"; url += "&v3=release-mgmt-account-bot%40mozilla.tld"; url += "&f4=bug_status"; url += "&o4=anywordssubstr"; url += "&v4=RESOLVED%2CVERIFIED%2CCLOSED"; break; } retrieveInfoFor(url, userQuery); } function retrieveInfoFor(url, userQuery) { $.ajax({ url: url, success: function (data) { populateBugs(url, userQuery, data); sortByDefault(); } }) .error(function (jqXHR, textStatus, errorThrown) { console.log("status:", textStatus); console.log("error thrown:", errorThrown); console.log("response text:", jqXHR.responseText); let info = JSON.parse(jqXHR.responseText); let text = info.message ? info.message : errorThrown; errorMsg(text); }); } function populateBugs(url, type, data) { if (!data || !data.bugs) { errorMsg('Response data was null. Unexpected error.'); return; } data.bugs.forEach(function (bug) { // Grab the first needinfo id for this user. This is not perfect since // we can have multiple. If the user clears an ni using the helpers in // this page, only the first will get cleared. Maybe we can fix this up // later. let id = getUserId(); let setter = 'unknown'; let flagId, flagCreationDate, flagIdx = -1; for (let idx = 0; idx < bug.flags.length; idx++) { if (bug.flags[idx].name != 'needinfo') continue; if (bug.flags[idx].requestee == id) { flagCreationDate = bug.flags[idx].creation_date; flagId = bug.flags[idx].id; flagIdx = idx; setter = bug.flags[idx].setter; break; } } if (flagIdx == -1) { errorMsg("Didn't find a flag that matched a needinfo we were looking for?? Bailing."); return true; } index = 0; let commentIdx = -1; bug.comments.every(function (comment) { if (flagCreationDate == comment.creation_time) { // when someone sets an ni without commenting, there won't be a comment to match here. // usually the right comment is the previous in the array (they forgot to set the ni when // submitting a comment) but lets not mess around with false positives here. leave it blank. //console.log(index, comment.creation_time, comment.creator); commentIdx = index; return false; } index++; return true; }); if (commentIdx == -1) { processRow(flagCreationDate, bug.id, flagId, flagIdx, bug.assigned_to, bug.severity, bug.priority, bug.op_sys, bug.flags, "", 0, bug.summary, setter); } else { processRow(flagCreationDate, bug.id, flagId, flagIdx, bug.assigned_to, bug.severity, bug.priority, bug.op_sys, bug.flags, bug.comments[commentIdx].text, bug.comments[commentIdx].count, bug.summary, setter); } }); } function addRec(ct, bugId, flagId, flagIdx, assignee, s, p, platform, msg, cmtIdx, title, flags, nisetter) { let record = { 'date': ct, // NI Date 'bugid': bugId, 'flagid': flagId, 'flagidx': flagIdx, 'assignee': assignee, 'title': title, 'severity': s, 'priority': p, 'platform': platform, 'flags': flags, 'msg': msg, 'commentid': cmtIdx, 'nisetter': nisetter, 'checked': false }; bugset.push(record); return record; } function processRow(ct, bugId, flagId, flagIdx, assignee, s, p, platform, flags, msg, cmtIdx, title, nisetter) { // flagId is the bugzilla flagid of the ni that set this user's ni. We use it // in comment links. let d = new Date(Date.parse(ct)); // comment simplification steps let msgClean = msg; let clipIdx = msg.indexOf('For more information'); if (clipIdx != -1) { msgClean = msg.substring(0, clipIdx); } if (platform == 'Unspecified') platform = ''; addRec(d, bugId, flagId, flagIdx, assignee, s, p, platform, msgClean, cmtIdx, title, flags, nisetter); } function prepPage(userQuery) { let header = "<div class='name-checkbox'><input type='checkbox' id='check-all' onclick='allCheckClick(this);'/></div>" + "<div class='name-nidate-hdr' onclick='dateSort();'>NI Date</div>" + "<div class='name-bugid-hdr' onclick='bugIdSort();'>Bug ID</div>" + "<div class='name-nifrom'>NeedInfo</div>" + "<div class='name-assignee'>Assignee</div>" + "<div class='name-severity-hdr' onclick='severitySort();'>Sev</div>" + "<div class='name-priority-hdr' onclick='prioritySort();'>Pri</div>" + "<div class='name-platform-hdr'>OS</div>" + "<div class='name-bugtitle'>Title</div>" + "<div class='name-nimsg'>NI Message</div>"; $("#report").append(header); let textHdr = ''; // odr, cdr, onb, cnb var userQuery = getUserQuery(); switch (userQuery) { case 'odr': textHdr = 'Open Dev Related' break; case 'cdr': textHdr = 'Closed Dev Related' break; case 'onb': textHdr = 'Open Nag Bot' break; case 'cnb': textHdr = 'Closed Nag Bot' break; } $("#title").text(textHdr + ' Details for ' + getUserId()); } function populateRow(record) { const options = { dateStyle: 'medium' }; let bugUrl = NeedInfoConfig.bugzilla_link_url.replace('{id}', record.bugid); let dateStr = record.date.toLocaleDateString(undefined, options); let tabTarget = NeedInfoConfig.targetnew ? "nidetails" : "_blank"; let bugLink = "<a target='" + tabTarget + "' href='" + bugUrl + "'>" + record.bugid + "</a>"; let titleLink = "<a class='nodecoration' target='" + tabTarget + "' href='" + bugUrl + "'>" + record.title + "</a>"; let commentLink = "<a class='nodecoration' target='" + tabTarget + "' href='" + bugUrl + "#c" + record.commentid + "'>" + record.msg + "</a>"; let assignee = trimAddress(record.assignee); let index = -1, first = true; let flagText = ''; let extraFlagText = ''; record.flags.forEach(function (flag) { index++; if (flag.name != 'needinfo') return true; if (record.flagidx == index) { flagText = trimAddress(flag.setter) + '<br/>'; } else { if (first) { first = false; extraFlagText = "<br/>additional nis:<br/>"; } extraFlagText += trimAddress(flag.setter) + ' &rArr; ' + trimAddress(flag.requestee) + '<br/>'; } }); let checkBox = "<input type='checkbox' onclick='checkClick(this);' id='check-" + record.bugid + "'"; if (record.checked) checkBox += " checked "; checkBox += "/>"; let content = "<div class='name-checkbox'>" + checkBox + "</div>" + "<div class='name-nidate'>" + dateStr + "</div>" + "<div class='name-bugid'>" + bugLink + "</div>" + "<div class='name-nifrom'>" + flagText + "<span class='name-nifromadd'>" + extraFlagText + "</span></div>" + "<div class='name-assignee'>" + assignee + "</div>" + "<div class='name-severity'>" + record.severity + "</div>" + "<div class='name-priority'>" + record.priority + "</div>" + "<div class='name-platform'>" + record.platform + "</div>" + "<div class='name-bugtitle'>" + titleLink + "</div>" + "<div class='name-nimsg'>" + commentLink + "</div>"; $("#report").append(content); } function checkConfig() { if (NeedInfoConfig.api_key.length == 0) { document.getElementById('alert-icon').style.visibility = 'visible'; } else { document.getElementById('alert-icon').style.visibility = 'hidden'; } } // Called from Settings util functions after settings are updated. function settingsUpdated() { checkConfig(); refreshList(null); } function clearRows() { $("#report").empty(); $("#errors").empty(); } function errorMsg(text) { $("#errors").append(text); } function populateRows() { document.getElementById('progress').style.visibility = 'hidden'; bugset.forEach(function (rec) { populateRow(rec); }); $("#stats").text("" + bugset.length + " Bugs"); updateButtonsState(); checkConfig(); } function refreshList(e) { if (e) { e.preventDefault(); } bugset = []; clearRows(); loadList(); } var sortTrack = { 'date': true, 'bugid': true, 'severity': true, 'priority': true, }; function updateDefaultSortSettings(type, order) { saveDefaultSortSettings(type, sortTrack[type]); } function sortByDefault() { let results = getDefaultSortSettings(); let type = 'date'; if (results.sort != null && results.order != null) { type = results.sort; sortTrack[type] = results.order == 'true' ? true:false; } // saved checked bugs saveDefaultSortSettings(type, sortTrack[type]); switch (type) { case 'date': dateSort(); break; case 'bugid': bugIdSort(); break; case 'severity': severitySort(); break; case 'priority': prioritySort(); break; }; } /* column title click handlers */ function updateCheckedState() { let checkedIds = getCheckedBugIds(); bugset.forEach((rec) => { rec.checked = false; }); if (checkedIds.length) { checkedIds.forEach((id) => { getBugRec(id).checked = true; }); } } function dateSort() { if (sortTrack['date']) { bugset.sort(sortDateAsc); } else { bugset.sort(sortDateDesc); } updateDefaultSortSettings('date', sortTrack['date']); sortTrack['date'] = !sortTrack['date']; updateCheckedState(); clearRows(); prepPage(); populateRows(); } function bugIdSort() { if (sortTrack['bugid']) { bugset.sort(sortBugIdAsc); } else { bugset.sort(sortBugIdDesc); } updateDefaultSortSettings('bugid', sortTrack['bugid']); sortTrack['bugid'] = !sortTrack['bugid']; updateCheckedState(); clearRows(); prepPage(); populateRows(); } function severitySort() { if (sortTrack['severity']) { bugset.sort(sortSeverityAsc); } else { bugset.sort(sortSeverityDesc); } updateDefaultSortSettings('severity', sortTrack['severity']); sortTrack['severity'] = !sortTrack['severity']; updateCheckedState(); clearRows(); prepPage(); populateRows(); } function prioritySort() { if (sortTrack['priority']) { bugset.sort(sortPriorityAsc); } else { bugset.sort(sortPriorityDesc); } updateDefaultSortSettings('priority', sortTrack['priority']); sortTrack['priority'] = !sortTrack['priority']; updateCheckedState(); clearRows(); prepPage(); populateRows(); } function updateButtonState(enabled) { buttons.forEach(function (button) { document.getElementById(button).disabled = !enabled; }); } function updateButtonsState() { let list = getCheckedBugIds(); let enabled = list.length > 0; let single = list.length == 1; if (NeedInfoConfig.api_key.length == 0) { enabled = false; } // blanket update all buttons updateButtonState(enabled); // spam blocking prevents this if (enabled) { document.getElementById('button-clearcmt').disabled = !single; } } // Always returns a valid list function getCheckedBugIds() { let list = []; bugset.every(function (bug) { let checkBox = document.getElementById('check-' + bug.bugid); if (checkBox == null) { //console.log('Mismatched check boxes with bug ids, something is messed up. Bailing.'); list = []; return false; } if (checkBox.checked) { list.push(bug.bugid); } return true; }); return list; } function clearCheckedBugs() { let list = getCheckedBugIds(); list.forEach(function (bugId) { let checkBox = document.getElementById('check-' + bugId); if (checkBox != null) { checkBox.checked = false; } }); } function allCheckClick(e) { let check = e.checked; bugset.forEach(function (rec) { rec.checked = e.checked ? true : false; }); clearRows(); prepPage(); populateRows(); document.getElementById('check-all').checked = check; updateButtonsState(); } function checkClick(e) { updateButtonsState(); } function getBugRec(bugId) { let record = null; bugset.every(function (rec) { if (rec.bugid == bugId) { record = rec; return false; } return true; }); return record; } function submitCommand(url, bugId, jsonData) { if (ChangeListTest) { return; } console.log("submitting changes to bugzilla:", bugId); $.ajax({ url: url, type: 'PUT', data: jsonData, contentType: "application/json", success: function (data) { // success response // Object { message: null, error: true, documentation: "http://www.bugzilla.org/docs/4.2/en/html/api/", code: 100500 } if (data && data.error) { console.log("bugzilla error on request:", data.code, "bug id:", bugId); updateAfterError(bugId, 'error code:' + data.code); } else { updateAfterChanges(bugId); } } }).error(function (jqXHR, textStatus, errorThrown) { console.log("status:", textStatus); console.log("error thrown:", errorThrown); console.log("response text:", jqXHR.responseText); try { let info = JSON.parse(jqXHR.responseText); let text = info.message ? info.message : errorThrown; updateAfterError(bugId, text); return; } catch(e) { } updateAfterError(bugId, errorThrown); }); } function updateStatus(percent) { let style = Math.ceil(percent) + '%'; document.getElementById('status').style.visibility = 'visible'; document.getElementById('status').style.backgroundSize = style; } function updateStatusText() { document.getElementById('status').textContent = ChangeList.length + " responses pending.."; } function updateAfterError(bugid, text) { ChangeList = ChangeList.filter((value) => value != bugid); updateStatus(((ChangeListSize - ChangeList.length) / ChangeListSize) * 100.0); updateStatusText(); errorMsg("request error for bug " + bugid + " '" + text + "'"); errorMsg("<br/>"); if (ChangeList.length == 0) { document.getElementById('status').style.visibility = 'collapse'; clearCheckedBugs(); updateButtonsState(); } } function updateAfterChanges(bugid) { ChangeList = ChangeList.filter((value) => value != bugid); updateStatus(((ChangeListSize - ChangeList.length) / ChangeListSize) * 100.0); updateStatusText(); if (ChangeList.length == 0) { refreshList(null); document.getElementById('status').style.visibility = 'collapse'; } } function queueBugChange(type, bugId, comment, to) { // change types: // clear-flag - valid params: none // redirect-flag - valid params: comment // redirect-flag-to - valid params: comment, to let data = null; let bug = getBugRec(bugId); if (bug == null) { console.log("Bug id not found in dataset! Something went wrong."); return; } // Testing UI progress meter for multiple bug changes if (ChangeListTest) { setTimeout(function () { updateAfterChanges(bugId); }, TestDelay); TestDelay += 500; return; } // https://bugzilla.readthedocs.io/en/latest/api/core/v1/bug.html#update-bug if (type == 'clear-flag') { data = { 'flags': [{ 'id': bug.flagid, 'status': 'X' }] }; if (comment != null) { data.comment = { 'body': comment }; } } else if (type == 'redirect-flag') { // redirect to setter with a comment data = { 'flags': [{ 'id': bug.flagid, 'status': 'X' }, { 'name': 'needinfo', 'status': '?', 'requestee': bug.nisetter, 'new': true, 'type_id': 800 }] }; if (comment != null) { data.comment = { 'body': comment }; } } else if (type == 'redirect-flag-to') { // redirect to with a comment data = { 'flags': [{ 'id': bug.flagid, 'status': 'X' }, { 'name': 'needinfo', 'status': '?', // doesn't work with aliases? 'requestee': to, 'new': true, 'type_id': 800 }] }; if (comment != null) { data.comment = { 'body': comment }; } } else { console.log("unsupported action.") return; } let json = JSON.stringify(data); let url = NeedInfoConfig.bugzilla_put_url.replace('{id}', bug.bugid); if (NeedInfoConfig.api_key.length) { url += "?api_key=" + NeedInfoConfig.api_key; } submitCommand(url, bugId, json); } function getRandomIntStr(max) { return '' + Math.floor(Math.random() * max); } function queueChanges(type, comment, to) { if (!ChangeList.length) return; // Temporarily disable buttons while we submit changes updateButtonState(false); ChangeListSize = ChangeList.length; TestDelay = 1000; // Show status updateStatus(0); updateStatusText(); ChangeList.forEach(function (bugId) { queueBugChange(type, bugId, comment, to); }); } // Warning dialog and elements // prompt-confirm // prompt-confirm-bugcount function invokeClearNI() { ChangeList = getCheckedBugIds(); if (!ChangeList.length) return; document.getElementById('prompt-confirm-bugcount').textContent = ChangeList.length; let dlg = document.getElementById("prompt-confirm"); dlg.returnValue = "cancel"; dlg.addEventListener('close', (event) => { if (dlg.returnValue == 'confirm') { queueChanges('clear-flag', null); } else { // Update buttons after cancel updateButtonsState(); } }, { once: true }); dlg.show(); } // Warning dialog and elements // prompt-comment-confirm // prompt-comment-confirm-bugcount // prompt-comment-confirm-comment function invokeClearNIWithComment() { ChangeList = getCheckedBugIds(); // due to spam protection, can't handle multiple bugs if (!ChangeList.length || ChangeList.length > 1) return; document.getElementById('prompt-comment-confirm-bugcount').textContent = ChangeList.length; let dlg = document.getElementById("prompt-comment-confirm"); dlg.returnValue = "cancel"; dlg.addEventListener('close', (event) => { if (dlg.returnValue == 'confirm') { let comment = document.getElementById("prompt-comment-confirm-comment").value; if (!comment.length) comment = null; queueChanges('clear-flag', comment); } else { // Update buttons after cancel updateButtonsState(); } }, { once: true }); dlg.show(); } // Warning dialog and elements // prompt-redirect-confirm // prompt-redirect-confirm-bugcount // prompt-redirect-confirm-comment function invokeRedirectToSetter() { ChangeList = getCheckedBugIds(); // Can handle multiple bugs, however if they set a comment, spam bocking // will probably get in the way. if (!ChangeList.length) return; let bug = getBugRec(ChangeList[0]); if (bug == null) return; document.getElementById('prompt-redirect-confirm-bugcount').textContent = ChangeList.length; document.getElementById('prompt-setter').textContent = trimAddress(bug.nisetter); let dlg = document.getElementById("prompt-redirect-confirm"); dlg.returnValue = "cancel"; dlg.addEventListener('close', (event) => { if (dlg.returnValue == 'confirm') { let comment = document.getElementById("prompt-redirect-confirm-comment").value; if (!comment.length) { comment = null; } queueChanges('redirect-flag', comment); } else { // Update buttons after cancel updateButtonsState(); } }, { once: true }); dlg.show(); } // Warning dialog and elements // prompt-redirect-to-confirm // prompt-redirect-to-confirm-bugcount // prompt-redirect-to-confirm-to // prompt-redirect-to-confirm-comment function invokeRedirectTo() { document.getElementById('autofill-user-search').disabled = true; document.getElementById("prompt-redirect-to-confirm-to").value = ""; $('#autofill-user-search').empty(); ChangeList = getCheckedBugIds(); // Can handle multiple bugs if (!ChangeList.length) return; let bug = getBugRec(ChangeList[0]); if (bug == null) return; document.getElementById('prompt-redirect-to-confirm-bugcount').textContent = ChangeList.length; let dlg = document.getElementById("prompt-redirect-to-confirm"); dlg.returnValue = "cancel"; dlg.addEventListener('close', (event) => { if (dlg.returnValue == 'confirm') { let comment = document.getElementById("prompt-redirect-to-confirm-comment").value; if (!comment.length) { comment = null; } let to = getRedirectToAccount(); queueChanges('redirect-flag-to', comment, to); } else { // Update buttons after cancel updateButtonsState(); } }, { once: true }); dlg.show(); } function submitUserSearch(value) { let url = NeedInfoConfig.bugzilla_user_url; url = url.replace('{value}', value); if (NeedInfoConfig.api_key.length) { url += "&api_key=" + NeedInfoConfig.api_key; } $('#autofill-user-search').empty(); $.ajax({ url: url, success: function (data) { // data.users.name and real_name data.users.forEach(function (val) { let name = "" + val.real_name; let email = "" + val.name; if (name.length == 0) { name = ' '; } name += ' (' + email + ')'; $('#autofill-user-search').append(new Option(name, email)); }); document.getElementById('autofill-user-search').disabled = false; } }) .error(function(jqXHR, textStatus, errorThrown) { console.log("status:", textStatus); console.log("error thrown:", errorThrown); console.log("response text:", jqXHR.responseText); try { let info = JSON.parse(jqXHR.responseText); let text = info.message ? info.message : errorThrown; errorMsg(text); return; } catch(e) { } errorMsg(errorThrown); }); } function getRedirectToAccount() { if (!document.getElementById('autofill-user-search').disabled && document.getElementById('autofill-user-search').value) { return document.getElementById('autofill-user-search').value; } let to = document.getElementById("prompt-redirect-to-confirm-to").value; if (!to.length) { to = null; } return to; } function searchForNick(element) { $('#autofill-user-search').empty(); document.getElementById('autofill-user-search').disabled = true; let value = element.value; if (!value) { return; } if (value.element < 3) { return; } console.log('searching for', value); submitUserSearch(value); } var SearchTimeoutId = -1; function onInputForBugzillaUser(element) { clearTimeout(SearchTimeoutId); SearchTimeoutId = setTimeout(function () { searchForNick(element); }, 750); }