app/bugzilla.mjs (160 lines of code) (raw):
import * as Global from "global";
export function queryURL(query, components, includeFields) {
const search = new URLSearchParams();
query.query_format = "advanced";
// add provided query parameters
let fieldNumber = 0;
for (const [name, value] of Object.entries(query)) {
if (name[0] === "f") {
if (name === "j_top") {
throw "Cannot set j_top, use a group with an OR joiner (f#=OP,j#=OR)";
}
const num = name.slice(1) * 1;
if (num > fieldNumber) {
fieldNumber = num;
}
search.append(name, value);
} else {
if (Array.isArray(value)) {
for (const v of value) {
search.append(name, v);
}
} else {
search.append(name, value);
}
}
}
// Add components. We can't use product= and component= query parameters as it
// hits on matching products OR components, rather than a product/component pair.
// Instead we build a query which does:
// .. ((product AND component) OR (product AND component) ...)
fieldNumber++;
search.append("f" + fieldNumber, "OP");
search.append("j" + fieldNumber, "OR");
if (components) {
for (const c of components) {
fieldNumber++;
search.append("f" + fieldNumber, "OP");
fieldNumber++;
search.append("f" + fieldNumber, "product");
search.append("o" + fieldNumber, "equals");
search.append("v" + fieldNumber, c.product);
fieldNumber++;
search.append("f" + fieldNumber, "component");
search.append("o" + fieldNumber, "equals");
search.append("v" + fieldNumber, c.component);
fieldNumber++;
search.append("f" + fieldNumber, "CP");
}
}
fieldNumber++;
search.append("f" + fieldNumber, "CP");
search.append(
"include_fields",
includeFields ||
[
"assigned_to",
"component",
"creation_time",
"creator",
"flags",
"groups",
"id",
"keywords",
"last_change_time",
"priority",
"product",
"severity",
"summary",
"triage_owner",
"type",
].join(","),
);
search.append("limit", "0");
return "bug?" + search.toString();
}
export function queryUrlToBuglistUrl(queryUrl, additional) {
// take a request endpoint url as generated by queryURL and return a full qualified buglist.cgi url
const params = new URLSearchParams(queryUrl.replace(/^bug\?/, ""));
params.delete("include_fields");
const nameMap = {
// eslint-disable-next-line camelcase
include_fields: "",
// eslint-disable-next-line camelcase
count_only: "",
limit: "",
severity: "bug_severity",
type: "bug_type",
};
for (const [name, newName] of Object.entries(nameMap)) {
if (params.has(name)) {
if (newName) {
params.set(newName, params.get(name));
}
params.delete(name);
}
}
if (additional) {
for (const [name, value] of Object.entries(additional)) {
params.set(name, value);
}
}
return `https://bugzilla.mozilla.org/buglist.cgi?${params.toString()}`;
}
export function buglistUrl(ids) {
return (
"https://bugzilla.mozilla.org/buglist.cgi?order=bug_list&bug_id=" +
ids.join(",")
);
}
export function setApiKey(key) {
window.localStorage.setItem("api-key", key);
}
export function getApiKey() {
return window.localStorage.getItem("api-key") || "";
}
export async function whoami() {
const response = await rest("whoami", undefined, true);
return response.error && response.code === 306 ? undefined : response;
}
export async function rest(endpoint, args, ignoreErrors, textResponse) {
let url = `https://bugzilla.mozilla.org/rest/${endpoint}`;
if (args) {
if (args instanceof Object) {
args = new URLSearchParams(args).toString();
}
url = url + "?" + args;
}
const apiKey = getApiKey();
const account = Global.getAccount();
const uaSuffix = account ? ` (${account.name})` : "";
const controller = new AbortController();
const timerId = setTimeout(() => controller.abort(), 60000);
let response;
try {
response = await fetch(url, {
method: "GET",
headers: {
"User-Agent": `bugdash${uaSuffix}`,
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "cross-site",
"X-Bugzilla-API-Key": apiKey,
},
signal: controller.signal,
});
} catch (error) {
clearTimeout(timerId);
controller.abort();
throw new Error(error.message);
}
const responseData = textResponse ? await response.text() : await response.json();
clearTimeout(timerId);
if (!response.ok && !responseData) {
throw new Error(response.statusText);
}
if (responseData.error && !ignoreErrors) {
throw new Error(responseData.message);
}
return responseData;
}