mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-01 06:36:39 +00:00
152 lines
4.4 KiB
JavaScript
152 lines
4.4 KiB
JavaScript
import fs from "node:fs/promises";
|
||
|
||
const decisions = JSON.parse(await fs.readFile("decisions.json", "utf8"));
|
||
const dryRun = String(process.env.DRY_RUN).toLowerCase() === "true";
|
||
|
||
const headers = {
|
||
Authorization: `Bearer ${process.env.GH_TOKEN}`,
|
||
Accept: "application/vnd.github+json",
|
||
"X-GitHub-Api-Version": "2022-11-28",
|
||
};
|
||
|
||
async function rest(url, method = "GET", body) {
|
||
const res = await fetch(url, {
|
||
method,
|
||
headers,
|
||
body: body ? JSON.stringify(body) : undefined
|
||
});
|
||
if (!res.ok) throw new Error(`${res.status} ${url}: ${await res.text()}`);
|
||
return res.status === 204 ? null : res.json();
|
||
}
|
||
|
||
async function graphql(query, variables) {
|
||
const res = await fetch("https://api.github.com/graphql", {
|
||
method: "POST",
|
||
headers,
|
||
body: JSON.stringify({ query, variables })
|
||
});
|
||
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
|
||
const json = await res.json();
|
||
if (json.errors) throw new Error(JSON.stringify(json.errors));
|
||
return json.data;
|
||
}
|
||
|
||
async function addLabel(owner, repo, issueNumber, labels) {
|
||
return rest(
|
||
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/labels`,
|
||
"POST",
|
||
{ labels }
|
||
);
|
||
}
|
||
|
||
async function addComment(owner, repo, issueNumber, body) {
|
||
return rest(
|
||
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
||
"POST",
|
||
{ body }
|
||
);
|
||
}
|
||
|
||
async function closeIssue(owner, repo, issueNumber) {
|
||
return rest(
|
||
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`,
|
||
"PATCH",
|
||
{ state: "closed", state_reason: "completed" }
|
||
);
|
||
}
|
||
|
||
async function getIssueNodeId(owner, repo, issueNumber) {
|
||
const issue = await rest(`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`);
|
||
return issue.node_id;
|
||
}
|
||
|
||
async function addToProject(issueNodeId) {
|
||
const mutation = `
|
||
mutation($projectId: ID!, $contentId: ID!) {
|
||
addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) {
|
||
item { id }
|
||
}
|
||
}
|
||
`;
|
||
|
||
const data = await graphql(mutation, {
|
||
projectId: process.env.PROJECT_ID,
|
||
contentId: issueNodeId
|
||
});
|
||
|
||
return data.addProjectV2ItemById.item.id;
|
||
}
|
||
|
||
async function setTextField(itemId, fieldId, value) {
|
||
const mutation = `
|
||
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: String!) {
|
||
updateProjectV2ItemFieldValue(input: {
|
||
projectId: $projectId,
|
||
itemId: $itemId,
|
||
fieldId: $fieldId,
|
||
value: { text: $value }
|
||
}) {
|
||
projectV2Item { id }
|
||
}
|
||
}
|
||
`;
|
||
|
||
return graphql(mutation, {
|
||
projectId: process.env.PROJECT_ID,
|
||
itemId,
|
||
fieldId,
|
||
value
|
||
});
|
||
}
|
||
|
||
for (const d of decisions) {
|
||
const [owner, repo] = d.repository.split("/");
|
||
|
||
if (d.final_decision === "AUTO_CLOSE") {
|
||
if (dryRun) continue;
|
||
|
||
await addLabel(owner, repo, d.issue_number, ["auto-closed-resolved"]);
|
||
await addComment(
|
||
owner,
|
||
repo,
|
||
d.issue_number,
|
||
d.model.close_comment ||
|
||
"This appears resolved based on linked evidence, so we’re closing it automatically. Reply if this still reproduces and we’ll reopen."
|
||
);
|
||
await closeIssue(owner, repo, d.issue_number);
|
||
}
|
||
|
||
if (d.final_decision === "MANUAL_REVIEW") {
|
||
await addLabel(owner, repo, d.issue_number, ["resolution-candidate"]);
|
||
|
||
const issueNodeId = await getIssueNodeId(owner, repo, d.issue_number);
|
||
const itemId = await addToProject(issueNodeId);
|
||
|
||
if (process.env.PROJECT_CONFIDENCE_FIELD_ID) {
|
||
await setTextField(itemId, process.env.PROJECT_CONFIDENCE_FIELD_ID, String(d.model.confidence));
|
||
}
|
||
if (process.env.PROJECT_REASON_FIELD_ID) {
|
||
await setTextField(itemId, process.env.PROJECT_REASON_FIELD_ID, d.model.reason_code);
|
||
}
|
||
if (process.env.PROJECT_EVIDENCE_FIELD_ID) {
|
||
await setTextField(itemId, process.env.PROJECT_EVIDENCE_FIELD_ID, d.issue_url);
|
||
}
|
||
if (process.env.PROJECT_LINKED_PR_FIELD_ID) {
|
||
const linked = (d.model.hard_signals || []).map(x => x.url).join(", ");
|
||
if (linked) {
|
||
await setTextField(itemId, process.env.PROJECT_LINKED_PR_FIELD_ID, linked);
|
||
}
|
||
}
|
||
if (process.env.PROJECT_REPO_FIELD_ID) {
|
||
await setTextField(itemId, process.env.PROJECT_REPO_FIELD_ID, d.repository);
|
||
}
|
||
|
||
await addComment(
|
||
owner,
|
||
repo,
|
||
d.issue_number,
|
||
d.model.manual_review_note ||
|
||
"This issue looks like a possible resolution candidate, but not with enough certainty for automatic closure. Added to the review queue."
|
||
);
|
||
}
|
||
} |