diff --git a/compose-db.js b/compose-db.js index 87dfec0..078587f 100644 --- a/compose-db.js +++ b/compose-db.js @@ -8,7 +8,15 @@ const authors = {}; const pulls = []; let page_count = 1; -const LINK_RE = /&page=([0-9]+)/g; +const API_LINK_RE = /&page=([0-9]+)/g; +// List of the keywords provided by https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue +const GH_MAGIC_KEYWORDS = [ + "close", "closes", "closed", + "fix", "fixes", "fixed", + "resolve", "resolves", "resolved", +]; +const GH_MAGIC_RE = RegExp("(" + GH_MAGIC_KEYWORDS.join("|") + ") ([a-z0-9-_]+/[a-z0-9-_]+)?#([0-9]+)", "gi"); +const GH_MAGIC_FULL_RE = RegExp("(" + GH_MAGIC_KEYWORDS.join("|") + ") https://github.com/([a-z0-9-_]+/[a-z0-9-_]+)/issues/([0-9]+)", "gi"); async function fetchPulls(page) { try { @@ -25,7 +33,7 @@ async function fetchPulls(page) { const links = res.headers.get("link").split(","); links.forEach((link) => { if (link.includes('rel="last"')) { - const matches = LINK_RE.exec(link); + const matches = API_LINK_RE.exec(link); if (matches && matches[1]) { page_count = Number(matches[1]); } @@ -61,6 +69,7 @@ function processPulls(pullsRaw) { "labels": [], "milestone": null, + "links": [], "teams": [], "reviewers": [], @@ -105,6 +114,9 @@ function processPulls(pullsRaw) { return 0; }); + // Look for linked issues in the body. + pr.links = extractLinkedIssues(item.body); + // Add teams, if available. if (item.requested_teams.length > 0) { item.requested_teams.forEach((teamItem) => { @@ -180,6 +192,44 @@ function processPulls(pullsRaw) { }); } +function extractLinkedIssues(pullBody) { + const links = []; + if (!pullBody) { + return links; + } + + const matches = [ + ...pullBody.matchAll(GH_MAGIC_RE), + ...pullBody.matchAll(GH_MAGIC_FULL_RE) + ]; + + matches.forEach((item) => { + let repository = item[2]; + if (!repository) { + repository = "godotengine/godot"; + } + + let keyword = item[1].toLowerCase(); + if (keyword.startsWith("clo")) { + keyword = "closes"; + } else if (keyword.startsWith("fix")) { + keyword = "fixes"; + } else if (keyword.startsWith("reso")) { + keyword = "resolves"; + } + + links.push({ + "full_match": item[0], + "keyword": keyword, + "repo": repository, + "issue": item[3], + "url": `https://github.com/${repository}/issues/${item[3]}`, + }); + }); + + return links; +} + async function main() { console.log("[*] Building local pull request database."); diff --git a/src/paths/index/components/prs/PullRequestItem.js b/src/paths/index/components/prs/PullRequestItem.js index c4756a6..d203943 100644 --- a/src/paths/index/components/prs/PullRequestItem.js +++ b/src/paths/index/components/prs/PullRequestItem.js @@ -128,6 +128,16 @@ export default class PullRequestItem extends LitElement { color: var(--draft-font-color); } + :host .pr-links { + font-size: 13px; + margin-top: 8px; + } + + :host .pr-link { + font-weight: 700; + white-space: nowrap; + } + :host .pr-stats { background-color: var(--stats-background-color); border-radius: 4px; @@ -208,6 +218,7 @@ export default class PullRequestItem extends LitElement { @property({ type: Array }) labels = []; @property({ type: String, reflect: true }) milestone = ''; @property({ type: String, reflect: true }) branch = ''; + @property({ type: Array }) links = []; @property({ type: String }) created_at = ''; @property({ type: String }) updated_at = ''; @property({ type: Object }) author = null; @@ -330,6 +341,32 @@ export default class PullRequestItem extends LitElement { + ${(this.links.length > 0 ? html` + + ` : '')} +