185 lines
4.9 KiB
JavaScript
185 lines
4.9 KiB
JavaScript
async function getMaintainers({ github, context }) {
|
|
const collaborators = await github.paginate(github.rest.repos.listCollaborators, {
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
});
|
|
return collaborators
|
|
.filter(
|
|
({ role_name, login }) =>
|
|
["admin", "maintain"].includes(role_name) ||
|
|
[
|
|
"alkispoly-db",
|
|
"AveshCSingh",
|
|
"danielseong1",
|
|
"smoorjani",
|
|
"SomtochiUmeh",
|
|
"xsh310",
|
|
].includes(login)
|
|
)
|
|
.map(({ login }) => login)
|
|
.sort();
|
|
}
|
|
|
|
async function getLinkedIssues({ github, owner, repo, prNumber }) {
|
|
const query = `
|
|
query($owner: String!, $repo: String!, $number: Int!) {
|
|
repository(owner: $owner, name: $repo) {
|
|
pullRequest(number: $number) {
|
|
createdAt
|
|
closingIssuesReferences(first: 10) {
|
|
nodes {
|
|
number
|
|
createdAt
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
const result = await github.graphql(query, {
|
|
owner,
|
|
repo,
|
|
number: prNumber,
|
|
});
|
|
|
|
return {
|
|
prCreatedAt: result.repository.pullRequest.createdAt,
|
|
issues: result.repository.pullRequest.closingIssuesReferences.nodes,
|
|
};
|
|
}
|
|
|
|
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
|
async function findFirstMaintainerInIssueComments({
|
|
github,
|
|
owner,
|
|
repo,
|
|
issueNumber,
|
|
maintainers,
|
|
}) {
|
|
for await (const response of github.paginate.iterator(github.rest.issues.listComments, {
|
|
owner,
|
|
repo,
|
|
issue_number: issueNumber,
|
|
})) {
|
|
for (const comment of response.data) {
|
|
if (maintainers.has(comment.user.login)) {
|
|
return comment.user.login;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function findMaintainerFromLinkedIssue({ github, owner, repo, prNumber, maintainers }) {
|
|
const { prCreatedAt, issues: linkedIssues } = await getLinkedIssues({
|
|
github,
|
|
owner,
|
|
repo,
|
|
prNumber,
|
|
});
|
|
|
|
if (linkedIssues.length !== 1 || !prCreatedAt) {
|
|
return null;
|
|
}
|
|
|
|
const linkedIssue = linkedIssues[0];
|
|
const prCreatedDate = new Date(prCreatedAt);
|
|
const issueCreatedDate = new Date(linkedIssue.createdAt);
|
|
|
|
if (prCreatedDate - issueCreatedDate > SEVEN_DAYS_MS) {
|
|
return null;
|
|
}
|
|
|
|
return findFirstMaintainerInIssueComments({
|
|
github,
|
|
owner,
|
|
repo,
|
|
issueNumber: linkedIssue.number,
|
|
maintainers,
|
|
});
|
|
}
|
|
|
|
module.exports = async ({ github, context, skipAssignment = false }) => {
|
|
const { owner, repo } = context.repo;
|
|
const maintainers = new Set(await getMaintainers({ github, context }));
|
|
|
|
// Get current time minus 200 minutes to look for recent comments and PRs
|
|
const lookbackTime = new Date(Date.now() - 200 * 60 * 1000);
|
|
|
|
// Use search API to find recently updated open PRs
|
|
const searchQuery = `repo:${owner}/${repo} is:pr is:open updated:>=${lookbackTime.toISOString()}`;
|
|
const searchResults = await github.paginate(github.rest.search.issuesAndPullRequests, {
|
|
q: searchQuery,
|
|
sort: "updated",
|
|
order: "desc",
|
|
});
|
|
|
|
console.log(`Scanning ${searchResults.length} recently updated PRs`);
|
|
|
|
for (const pr of searchResults) {
|
|
// Get recent comments and reviews
|
|
const issueComments = await github.rest.issues.listComments({
|
|
owner,
|
|
repo,
|
|
issue_number: pr.number,
|
|
since: lookbackTime.toISOString(),
|
|
});
|
|
const reviews = await github.rest.pulls.listReviews({
|
|
owner,
|
|
repo,
|
|
pull_number: pr.number,
|
|
});
|
|
|
|
// Filter reviews by lookback time and extract authors
|
|
const recentReviews = reviews.data.filter((r) => new Date(r.submitted_at) > lookbackTime);
|
|
const commentAuthors = new Set([
|
|
...issueComments.data.map((c) => c.user.login),
|
|
...recentReviews.map((r) => r.user.login),
|
|
]);
|
|
|
|
// Use Set operations to find maintainers to assign
|
|
const prAuthor = pr.user.login;
|
|
const currentAssignees = new Set(pr.assignees.map((a) => a.login));
|
|
const excludeSet = new Set([prAuthor, ...currentAssignees]);
|
|
|
|
let maintainersToAssign = [...commentAuthors.intersection(maintainers).difference(excludeSet)];
|
|
|
|
// Fall back to linked issue comments if no maintainers found from recent PR activity
|
|
if (maintainersToAssign.length === 0) {
|
|
const maintainer = await findMaintainerFromLinkedIssue({
|
|
github,
|
|
owner,
|
|
repo,
|
|
prNumber: pr.number,
|
|
maintainers,
|
|
});
|
|
if (maintainer && !excludeSet.has(maintainer)) {
|
|
maintainersToAssign = [maintainer];
|
|
}
|
|
}
|
|
|
|
if (maintainersToAssign.length === 0) {
|
|
continue;
|
|
}
|
|
|
|
// Assign maintainers
|
|
if (!skipAssignment) {
|
|
await github.rest.issues.addAssignees({
|
|
owner,
|
|
repo,
|
|
issue_number: pr.number,
|
|
assignees: maintainersToAssign,
|
|
});
|
|
}
|
|
console.log(
|
|
`${skipAssignment ? "[DRY RUN] Would assign" : "Assigned"} [${maintainersToAssign.join(
|
|
", "
|
|
)}] to PR #${pr.number}`
|
|
);
|
|
}
|
|
|
|
console.log("Scan completed");
|
|
};
|