gh-13085: add support for new gh pull request dashboard (gh-13090)

This commit is contained in:
Slowlife
2026-04-10 01:22:06 +07:00
committed by GitHub
parent 4ca83bfe33
commit a2a64cec6a
2 changed files with 159 additions and 95 deletions

View File

@@ -122,7 +122,7 @@ export class nsZenLiveFolderProvider {
this.manager.saveState();
}
fetch(url, { maxContentLength = 5 * 1024 * 1024 } = {}) {
fetch(url, { maxContentLength = 5 * 1024 * 1024, headers = {} } = {}) {
const uri = lazy.NetUtil.newURI(url);
// TODO: Support userContextId when fetching, it should be inherited from the folder's
// current space context ID.
@@ -155,6 +155,10 @@ export class nsZenLiveFolderProvider {
triggeringPrincipal: principal,
}).QueryInterface(Ci.nsIHttpChannel);
for (const [name, value] of Object.entries(headers)) {
channel.setRequestHeader(name, value, false);
}
let httpStatus = null;
let contentType = "";
let headerCharset = null;

View File

@@ -32,6 +32,26 @@ export class nsGithubLiveFolderProvider extends nsZenLiveFolderProvider {
return "zen-live-folder-github-no-filter";
}
if (
this.state.type === "pull-requests" &&
typeof this.state.isJsonApi !== "boolean"
) {
const { text, status } = await this.fetch(this.state.url, {
headers: {
Accept: "application/json,text/html",
},
});
if (status === 404) {
return "zen-live-folder-github-no-auth";
}
try {
JSON.parse(text);
this.state.isJsonApi = true;
} catch {
this.state.isJsonApi = false;
}
}
const queries = this.#buildSearchOptions();
const requests = await Promise.all(
queries.map(query => {
@@ -56,11 +76,15 @@ export class nsGithubLiveFolderProvider extends nsZenLiveFolderProvider {
}
if (items) {
items.forEach(item => combinedItems.set(item.id, item));
for (const item of items) {
combinedItems.set(item.id, item);
}
}
if (activeRepos) {
activeRepos.forEach(repo => combinedActiveRepos.add(repo));
for (const repo of activeRepos) {
combinedActiveRepos.add(repo);
}
}
}
@@ -73,39 +97,44 @@ export class nsGithubLiveFolderProvider extends nsZenLiveFolderProvider {
}
async parsePullRequests(url) {
const { text, status } = await this.fetch(url);
const { text, status } = await this.fetch(url, {
headers: {
Accept: "application/json,text/html",
},
});
if (status !== 200) {
return { status };
}
let parsedJson = null;
try {
const document = new DOMParser().parseFromString(text, "text/html");
const issues = document.querySelectorAll("div[id^=issue_]");
parsedJson = JSON.parse(text);
this.state.isJsonApi = true;
} catch {
if (this.state.isJsonApi) {
this.state.isJsonApi = false;
// throw to indicate user to re-try (Url may contain invalid params for non-json /pulls)
throw new Error("Unexpected content type");
}
}
if (parsedJson) {
const results =
parsedJson.payload.pullsDashboardSurfaceContentRoute.results;
const items = [];
const activeRepos = [];
const activeRepos = new Set();
if (issues.length) {
const authors = document.querySelectorAll(".opened-by a");
const titles = document.querySelectorAll("a[id^=issue_]");
for (let i = 0; i < issues.length; i++) {
const author = authors[i].textContent;
const title = titles[i].textContent;
const repo = titles[i].previousElementSibling.textContent.trim();
if (repo) {
activeRepos.push(repo);
}
const idMatch = authors[i].parentElement.textContent
.match(/#[0-9]+/)
.shift();
items.push({
title,
subtitle: author,
icon: "chrome://browser/content/zen-images/favicons/github.svg",
url: new URL(titles[i].href, this.state.url),
id: `${repo}${idMatch}`,
});
}
for (const pr of results) {
activeRepos.add(pr.repoNameWithOwner);
items.push({
id: `${pr.repoNameWithOwner}#${pr.number}`,
title: pr.title,
subtitle: pr.author.displayLogin,
icon: "chrome://browser/content/zen-images/favicons/github.svg",
url: pr.permalink,
});
}
return {
@@ -114,78 +143,109 @@ export class nsGithubLiveFolderProvider extends nsZenLiveFolderProvider {
items,
activeRepos,
};
} catch (err) {
console.error("Failed to parse Github pull requests", err);
return {
status,
};
}
const document = new DOMParser().parseFromString(text, "text/html");
const issues = document.querySelectorAll("div[id^=issue_]");
const items = [];
const activeRepos = new Set();
if (issues.length) {
const authors = document.querySelectorAll(".opened-by a");
const titles = document.querySelectorAll("a[id^=issue_]");
for (let i = 0; i < issues.length; i++) {
const author = authors[i].textContent;
const title = titles[i].textContent;
const repo = titles[i].previousElementSibling.textContent.trim();
if (repo) {
activeRepos.add(repo);
}
const idMatch = authors[i].parentElement.textContent
.match(/#[0-9]+/)
.shift();
items.push({
title,
subtitle: author,
icon: "chrome://browser/content/zen-images/favicons/github.svg",
url: new URL(titles[i].href, this.state.url),
id: `${repo}${idMatch}`,
});
}
}
return {
status,
items,
activeRepos,
};
}
async parseIssues(url) {
const { text, status } = await this.fetch(url);
try {
const document = new DOMParser().parseFromString(text, "text/html");
const issues = document.querySelectorAll(
"div[class^=IssueItem-module__defaultRepoContainer]"
);
const items = [];
const activeRepos = [];
if (issues.length) {
const authors = document.querySelectorAll(
"a[class^=IssueItem-module__authorCreatedLink]"
);
const titles = document.querySelectorAll(
"div[class^=Title-module__container]"
);
const links = document.querySelectorAll(
'[data-testid="issue-pr-title-link"]'
);
for (let i = 0; i < issues.length; i++) {
const [rawRepo, rawNumber] = issues[i].childNodes;
const author = authors[i]?.textContent;
const title = titles[i]?.textContent;
const issueUrl = links[i]?.href;
const repo = rawRepo.textContent?.trim();
if (repo) {
activeRepos.push(repo);
}
const numberMatch = rawNumber?.textContent?.match(/[0-9]+/);
const number = numberMatch?.[0] ?? "";
items.push({
title,
subtitle: author,
icon: "chrome://browser/content/zen-images/favicons/github.svg",
url: "https://github.com" + issueUrl,
id: `${repo}#${number}`,
});
}
}
return {
status,
items,
activeRepos,
};
} catch (err) {
console.error("Failed to parse Github Issues", err);
return {
status,
};
if (status !== 200) {
return { status };
}
const document = new DOMParser().parseFromString(text, "text/html");
const issues = document.querySelectorAll(
"div[class^=IssueItem-module__defaultRepoContainer]"
);
const items = [];
const activeRepos = new Set();
if (issues.length) {
const authors = document.querySelectorAll(
"a[class^=IssueItem-module__authorCreatedLink]"
);
const titles = document.querySelectorAll(
"div[class^=Title-module__container]"
);
const links = document.querySelectorAll(
'[data-testid="issue-pr-title-link"]'
);
for (let i = 0; i < issues.length; i++) {
const [rawRepo, rawNumber] = issues[i].childNodes;
const author = authors[i]?.textContent;
const title = titles[i]?.textContent;
const issueUrl = links[i]?.href;
const repo = rawRepo.textContent?.trim();
if (repo) {
activeRepos.add(repo);
}
const numberMatch = rawNumber?.textContent?.match(/[0-9]+/);
const number = numberMatch?.[0] ?? "";
items.push({
title,
subtitle: author,
icon: "chrome://browser/content/zen-images/favicons/github.svg",
url: "https://github.com" + issueUrl,
id: `${repo}#${number}`,
});
}
}
return {
status,
items,
activeRepos,
};
}
#buildSearchOptions() {
const baseQuery = [
this.state.type === "pull-requests" ? "is:pr" : "is:issue",
"state:open",
"is:open",
"sort:updated-desc",
];
@@ -219,7 +279,7 @@ export class nsGithubLiveFolderProvider extends nsZenLiveFolderProvider {
}
const searchParams = [];
if (this.state.type === "pull-requests") {
if (this.state.type === "pull-requests" && !this.state.isJsonApi) {
for (const query of queries) {
searchParams.push(`${baseQuery.join(" ")} ${query}`);
}
@@ -227,8 +287,8 @@ export class nsGithubLiveFolderProvider extends nsZenLiveFolderProvider {
return searchParams;
}
// type: issues
return [`${baseQuery.join(" ")} ${queries.join(" OR ")}`];
// type: issues or pull requests json api
return [`${baseQuery.join(" ")} (${queries.join(" OR ")})`];
}
get options() {