diff --git a/scanner/github.ts b/scanner/github.ts index 68622f0..f9e65f6 100644 --- a/scanner/github.ts +++ b/scanner/github.ts @@ -329,6 +329,7 @@ export async function fetchAllComments(needAllComments: IssueOrPr[], needEarlyCo } const Repository = z.object({ + nameWithOwner: z.string(), labels: z.object({ totalCount: z.number(), nodes: z.array(z.object({ @@ -346,6 +347,7 @@ export async function getRepo(org, repo): Promise { const result = RepositoryQueryResult.parse(await octokit.graphql( gql(`query ($owner: String!, $repoName: String!) { repository(owner: $owner, name: $repoName) { + nameWithOwner labels(first: 100, query: "Priority") { totalCount nodes { diff --git a/scanner/main.ts b/scanner/main.ts index 56d99bd..51fb550 100644 --- a/scanner/main.ts +++ b/scanner/main.ts @@ -59,7 +59,7 @@ async function analyzeRepo(org: string, repoName: string, globalStats: GlobalSta author: issue.author?.login, createdAt: issue.createdAt, sloTimeUsed: Temporal.Duration.from({ seconds: 0 }), - whichSlo: whichSlo(issue), + whichSlo: whichSlo(repo.nameWithOwner, issue), onAgendaFor: countAgendaTime(issue, now), labels: issue.labels.nodes.map(label => label.name), stats: { diff --git a/scanner/per-repo.ts b/scanner/per-repo.ts new file mode 100644 index 0000000..eba9780 --- /dev/null +++ b/scanner/per-repo.ts @@ -0,0 +1,18 @@ +import { IssueOrPr } from "./github.js"; + +const triagePredicates: Record<`${string}/${string}`, (issue: Pick) => boolean> = { + 'w3c/csswg-drafts': function (issue: Pick) { + return issue.labels.nodes.length > 0; + }, +}; + +// True if this repository has configured a custom function to say whether an issue is triaged +// without an SLO, instead of the common `Priority: Eventually` label. +export function hasTriagePredicate(repoNameWithOwner: string) { + return repoNameWithOwner in triagePredicates; +} + +// True if this issue should be considered triaged without an SLO. +export function isTriaged(repoNameWithOwner: string, issue: Pick): boolean { + return triagePredicates[repoNameWithOwner]?.(issue); +} diff --git a/scanner/slo.ts b/scanner/slo.ts index b62df26..a426f50 100644 --- a/scanner/slo.ts +++ b/scanner/slo.ts @@ -2,6 +2,7 @@ import { Temporal } from "@js-temporal/polyfill"; import { SloType } from '@lib/repo-summaries.js'; import assert from "node:assert"; import type { IssueOrPr, Repository } from "./github.js"; +import { hasTriagePredicate, isTriaged } from "./per-repo.js"; const PRIORITY_URGENT = "priority: urgent"; const PRIORITY_SOON = "priority: soon"; @@ -12,19 +13,21 @@ export function NeedsReporterFeedback(label: string) { return label.toLowerCase() === NEEDS_REPORTER_FEEDBACK; } -/** Returns whether `repo` has enough labels to mark bugs as triaged. +/** Returns whether `repo` has enough labels or configuration to mark bugs as triaged. * * Because different repositories will adopt different subsets of the labels this tool recognizes, * we should only look for the smallest subset that indicates the repo isn't relying on the triage * heuristics. For now, that's just the `Priority: Eventually` label. + * + * It's also possible to define a custom isTriaged predicate for each repository. */ -export function hasLabels(repo: Pick): boolean { - return repo.labels.nodes.some(labelNode => labelNode.name === PRIORITY_EVENTUALLY); +export function hasLabels(repo: Pick): boolean { + return hasTriagePredicate(repo.nameWithOwner) || repo.labels.nodes.some(labelNode => labelNode.name === PRIORITY_EVENTUALLY); } -export function whichSlo(issue: Pick): SloType { +export function whichSlo(repoNameWithOwner: string, issue: Pick): SloType { const labels: string[] = issue.labels.nodes.map(label => label.name.toLowerCase()); - if (issue.isDraft || labels.includes(PRIORITY_EVENTUALLY) || labels.includes(NEEDS_REPORTER_FEEDBACK)) { + if (issue.isDraft || labels.includes(NEEDS_REPORTER_FEEDBACK)) { return "none"; } if (labels.includes(PRIORITY_URGENT)) { @@ -33,6 +36,9 @@ export function whichSlo(issue: Pick): SloType if (labels.includes(PRIORITY_SOON)) { return "soon"; } + if (labels.includes(PRIORITY_EVENTUALLY) || isTriaged(repoNameWithOwner, issue)) { + return "none"; + } return "triage"; }