js/utils.js (334 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 SettingsLoaded = false;
var TriageConfig = {
jsonConfig: null,
persistStorage: true,
apiKey: null,
lastTeamOrIndividual: null,
useSameTarget: true,
listConfig: {
filter_mynis: false,
filter_allnis: false
}
};
/*
* Basic Utility Finctions
*/
function getYear(now) {
var year = $.url().param('year');
if (year) {
if (parseInt(year)) {
return year;
}
}
return "" + now.getFullYear();
}
function getTeam() {
TriageConfig.lastTeamOrIndividual = $.url().param('team');
storeInStorage('lastTeamOrIndividual', TriageConfig.lastTeamOrIndividual);
return TriageConfig.lastTeamOrIndividual;
}
function replaceUrlParam(url, paramName, paramValue) {
if (paramValue == null) {
paramValue = '';
}
var pattern = new RegExp('\\b(' + paramName + '=).*?(&|#|$)');
if (url.search(pattern) >= 0) {
return url.replace(pattern, '$1' + paramValue + '$2');
}
url = url.replace(/[?#]$/, '');
return url + (url.indexOf('?') > 0 ? '&' : '?') + paramName + '=' + paramValue;
}
function updateApiKeyIcon() {
let key = getAPIKeyFromStorage();
if (key == null || !key.length) {
document.getElementById('alert-icon').style.visibility = 'visible';
} else {
document.getElementById('alert-icon').style.visibility = 'hidden';
}
}
/*
* Parsing functions
*/
function trimAddress(account) {
if (account == undefined) {
// Unassigned
account = '';
}
account = account.replace('nobody@mozilla.org', 'nobody');
// aryx.bugmail@gmx-topmail.de
account = account.replace('aryx.bugmail@gmx-topmail.de', 'Aryx');
// ryanvm@gmail.com
account = account.replace('ryanvm@gmail.com', 'RyanVM');
// nagbot
account = account.replace('release-mgmt-account-bot@mozilla.tld', 'nag-bot');
// updatebot
account = account.replace('update-bot@bmo.tld', 'update-bot');
account = account.replace('@mozilla.org', '@moz');
account = account.replace('@mozilla.com', '@moz');
return account;
}
// Update libjxl to new version 5853ad97044c3b9da46d10b611e66063b1297cc5 from 2022-12-22 12:47:29
var RegExpSummaryPattern1 = new RegExp('Update (.*) to new version (.*) from (.*)');
// Update dav1d to new version ddbbfde for Firefox 91
var RegExpSummaryPattern2 = new RegExp('Update (.*) to new version (.*) for .*');
// Examine angle for 2 new commits, culminating in 92b793976c27682baaac6ea07f56d079b837876c (2021-10-12 23:36:02 +0000)
var RegExpSummaryPattern3 = new RegExp('Examine (.*) for [0-9]+ new commits, culminating in ([a-z0-9]+) ([0-9-]+)');
function parseBugSummary(bugid, summary, assignee, creation_time, resolution) {
let data = {
'rev': 'unknown',
'date': new Date(creation_time),
'lib': 'unknown',
'id': bugid.toString(),
'resolution': resolution,
'assignee': trimAddress(assignee),
summary: summary
};
// bleh
summary = summary.replace('(', '');
summary = summary.replace(')', '');
let results = RegExpSummaryPattern1.exec(summary);
if (results != null) {
data.lib = results[1];
data.rev = results[2];
data.date = new Date(results[3]);
return data;
}
results = RegExpSummaryPattern2.exec(summary);
if (results != null) {
data.lib = results[1];
data.rev = results[2];
return data;
}
results = RegExpSummaryPattern3.exec(summary);
if (results != null) {
data.lib = results[1];
data.rev = results[2];
data.date = new Date(results[3]);
return data;
}
errorMsg('Error parsing bug', bugid, 'summary:');
errorMsg(summary);
return null;
}
function dateToBz(date) {
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
}
/*
* Returns a data structure containing all of the ics entries,
* names, dates, and some general stats in the root object.
*/
function parseICSData(icsdata) {
let triageData = {};
// Download calendar and parse into bugqueries.
let ics = ical.parseICS(icsdata);
let years = [];
let maxDate = null, minDate = null;
for (let k in ics) {
if (!ics.hasOwnProperty(k)) {
console.log('no Own Property', k)
}
if (ics[k].type != 'VEVENT') {
console.log('Not a VEVENT', ics[k])
continue;
}
var ev = ics[k];
//console.log(ev.summary, ev.location, ev.start.getDate(), MONTHS[ev.start.getMonth()], ev.start.getFullYear());
// Filter entries based on team name:
// teams - webrtc, media, graphics
let team = getTeam()
let summary = ev.summary.toLowerCase()
if (team == 'webrtc' && summary.indexOf('webrtc') == -1) {
continue;
}
if (team == 'media' &&
(summary.indexOf('playback') == -1 && summary.indexOf('media') == -1)
) {
continue;
}
var who = ev.summary;
var startDate = dateToBz(ev.start);
var endDate = dateToBz(ev.end);
var year = `${ev.start.getFullYear()}`;
var endyear = `${ev.end.getFullYear()}`;
if (parseInt(year) < 2022) {
continue;
}
years.push(year);
if (maxDate < ev.end || maxDate == null)
maxDate = ev.end;
if (minDate > ev.start || minDate == null)
minDate = ev.start;
// Cleanup summaries a bit
who = who.replace('webrtc triage', '');
who = who.replace('playback triage', '');
who = who.replace('[Incoming Triage] ', '');
who = who.replace('WebRTC Triage', '');
who = who.replace('Media Triage', '');
//console.log('parseICS event:', '"' + who + '"', startDate, endDate, notAfterBz, year, endyear);
if (!triageData[year]) {
triageData[year] = {
data: [],
search: '',
updayebot: ''
}
}
if (!triageData[endyear]) {
triageData[endyear] = {
data: [],
search: '',
updayebot: ''
}
}
const query = {
who: who,
from: startDate,
to: endDate
};
triageData[year].data.push(query);
if (year != endyear) {
triageData[endyear].data.push(query);
}
}
triageData['min'] = dateToBz(minDate);
triageData['max'] = dateToBz(maxDate);
//console.log(triageData.min, triageData.max);
// Sort
years.forEach(function (date) {
triageData[date].data.sort(
function(a, b){
return new Date(a.from) > new Date(b.from);
});
});
return triageData;
}
function setupQueryURLs(displayFuture) {
if (!BugQueries) {
console.log("no bug queries found.")
return 0;
}
let components;
switch (getTeam()) {
case 'graphics':
components = TriageConfig.jsonConfig.graphics_components;
break;
case 'media':
components = TriageConfig.jsonConfig.media_components;
break;
case 'webrtc':
components = TriageConfig.jsonConfig.webrtc_components;
break;
}
// Do not show results for dates that are too close to today. Only once we
// are five days after the end of the term...
var cutoff = new Date();
for (var i = 0; i < BugQueries.length; i++) {
// If this is the schedule, display all queries for the year, otherwise filter
// out future queries.
if (!displayFuture) {
var dto = new Date(BugQueries[i].from);
if (cutoff < dto) {
continue;
}
}
let search_params = "?";
let ubsearch = "?"
TriageConfig.jsonConfig.generic_bugzilla_search_template.forEach(function (entry) {
search_params += entry + "&";
ubsearch += entry + "&";
});
TriageConfig.jsonConfig.additional_bugzilla_search_params.forEach(function (entry) {
search_params += entry + "&";
});
TriageConfig.jsonConfig.additional_updatebot_search_params.forEach(function (entry) {
ubsearch += entry + "&";
});
//TriageConfig.jsonConfig.filtermynis_search_params.forEach(function (entry) {
// search_params += entry + "&";
//});
// --------------------------------------------------------
// ICS dates should span 8 days since they get plugged
// directly into bugzilla searches.
// Bugzilla searches EXCLUDE start date, INCLUDE end date.
// console.log(BugQueries[i].from, '->', BugQueries[i].to)
// --------------------------------------------------------
search_params = search_params.replace(/<COMPONENT>/g, components);
search_params = search_params.replace(/<AFTER>/g, BugQueries[i].from).replace(/<NOT-AFTER>/g, BugQueries[i].to);
BugQueries[i]["url"] = search_params;
// Bugzilla updatebot searches
ubsearch = ubsearch.replace(/<COMPONENT>/g, components);
ubsearch = ubsearch.replace(/<AFTER>/g, BugQueries[i].from).replace(/<NOT-AFTER>/g, BugQueries[i].to);
BugQueries[i]["uburl"] = ubsearch;
}
// Global searches
let search_params = "?";
let ubsearch = "?"
TriageConfig.jsonConfig.generic_bugzilla_search_template.forEach(function (entry) {
search_params += entry + "&";
ubsearch += entry + "&";
});
TriageConfig.jsonConfig.additional_bugzilla_search_params.forEach(function (entry) {
search_params += entry + "&";
});
TriageConfig.jsonConfig.additional_updatebot_search_params.forEach(function (entry) {
ubsearch += entry + "&";
});
//TriageConfig.jsonConfig.filtermynis_search_params.forEach(function (entry) {
// search_params += entry + "&";
//});
// Bugzilla searches
search_params = search_params.replace(/<COMPONENT>/g, components);
search_params = search_params.replace('<AFTER>', TriageData.min).replace('<NOT-AFTER>', TriageData.max);
TriageData["url"] = search_params;
// Bugzilla updatebot searches
ubsearch = ubsearch.replace(/<COMPONENT>/g, components);
ubsearch = ubsearch.replace(/<AFTER>/g, TriageData.min).replace(/<NOT-AFTER>/g, TriageData.max);
TriageData["uburl"] = ubsearch;
// console.log(ubsearch);
return BugQueries.length;
}
/*
* Storage utilities
*/
function localStorageItemExists(keyname) {
let value = localStorage.getItem(keyname);
return !(value == null || !value.length);
}
function getFromStorage(keyname) {
let st = useLocalStore() ? localStorage : sessionStorage;
let value = st.getItem(keyname);
if (value == null || !value.length) {
console.log("session storage value for '" + keyname + "' does not exist.");
return null;
}
return value;
}
function clearStorage(keyname) {
localStorage.removeItem(keyname);
sessionStorage.removeItem(keyname);
}
function storeInStorage(keyname, value) {
AssertSettings();
let st = useLocalStore() ? localStorage : sessionStorage;
st.setItem(keyname, value);
}
function getAPIKeyFromStorage() {
return getFromStorage('apiKey');
}
function AssertSettings() {
if (!SettingsLoaded) console.log("Assert: settings used before being loaded.")
}
function useLocalStore() {
AssertSettings();
return TriageConfig.persistStorage;
}
/*
* Settings panel processing
*/
/*
var TriageConfig = {
jsonConfig: '', // json config file data
persistStorage: true,
apiKey: null,
lastTeamOrIndividual: null,
useSameTarget: true,
listConfig: {
filter_mynis: false,
filter_allnis: false
}
};
*/
// Load TriageConfig with persisted data from session store, or
// populate with default values.
function loadSettingsInternal(jsonConfig) {
console.log('loading settings...');
SettingsLoaded = true;
TriageConfig.jsonConfig = jsonConfig;
if (!localStorageItemExists('persistStorage')) {
TriageConfig.persistStorage = true;
TriageConfig.useSameTarget = true;
TriageConfig.listConfig.filter_mynis = false;
TriageConfig.listConfig.filter_allnis = false;
return;
}
TriageConfig.persistStorage = getFromStorage("persistStorage") == (null || 'false') ? false : true;
if (getFromStorage("lastTeamOrIndividual") == null) {
TriageConfig.lastTeamOrIndividual = 'media';
} else {
TriageConfig.lastTeamOrIndividual = getFromStorage("lastTeamOrIndividual");
}
TriageConfig.apiKey = (getAPIKeyFromStorage() == null) ? "" : getAPIKeyFromStorage();
TriageConfig.useSameTarget = getFromStorage("useSameTarget");
TriageConfig.listConfig.filter_mynis = getFromStorage("filter_mynis") == (null || 'false') ? false : true;
TriageConfig.listConfig.filter_allnis = getFromStorage("filter_allnis") == (null || 'false') ? false : true;
console.log('config:', TriageConfig);
}
function openSettingsPanel() {
AssertSettings();
let dlg = document.getElementById("prompt-query-account");
dlg.returnValue = "cancel";
if (TriageConfig.apiKey != null) {
document.getElementById('api-key').value = TriageConfig.apiKey;
}
//document.getElementById("option-filter_allnis").checked = TriageConfig.listConfig.filter_allnis;
//document.getElementById("option-filter_mynis").checked = TriageConfig.listConfig.filter_mynis;
//document.getElementById("option-targets").checked = TriageConfig.useSameTarget;
document.getElementById("option-save").checked = TriageConfig.persistStorage;
dlg.addEventListener('close', (event) => {
if (onSettingsClosed != undefined) {
onSettingsClosed();
}
if (dlg.returnValue == 'confirm') {
saveSettingsInternal();
}
}, { once: true });
if (onSettingsOpened != undefined) {
onSettingsOpened();
}
dlg.show();
}
function getDlgValue(id) {
return document.getElementById(id).value;
}
function getDlgToggleState(id) {
return document.getElementById(id).checked;
}
// Extract values from settings dialog and poulate TriageConfig. Also
// save data in persisted or local storage.
function saveSettingsInternal() {
AssertSettings();
TriageConfig.persistStorage = getDlgValue('option-save') == (null || 'false') ? false : true;
TriageConfig.apiKey = getDlgValue('api-key');
//TriageConfig.listConfig.filter_allnis = getDlgToggleState('option-filter_allnis');
//TriageConfig.listConfig.filter_mynis = getDlgToggleState('option-filter_mynis');
//TriageConfig.useSameTarget = getDlgToggleState('option-targets');
//storeInStorage('useSameTarget', TriageConfig.useSameTarget);
storeInStorage('apiKey', TriageConfig.apiKey);
storeInStorage('persistStorage', TriageConfig.persistStorage);
//storeInStorage('filter_allnis', TriageConfig.listConfig.filter_allnis);
//storeInStorage('filter_mynis', TriageConfig.listConfig.filter_mynis);
updateApiKeyIcon();
}
/*
<div class="settings-col1">
<input type="checkbox" id="option-filter_allnis" name="target" />
</div>
<div class="settings-col2">
<div class="settings-label">
Filter bugs that have pending personal need info flags set (ni's set by people vs. bots).
</div>
</div>
<div class="settings-col1">
<input type="checkbox" id="option-filter_mynis" name="target" />
</div>
<div class="settings-col2">
<div class="settings-label">
Filter bugs that currently have a need info set by you.
</div>
</div>
*/
/*
* Event handlers
*/
function teamSelectionChanged(el) {
var team = el.options[el.selectedIndex].value;
window.location.href = replaceUrlParam(window.location.href, 'team', team);
}