mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-17 07:21:58 +00:00
154 lines
6.4 KiB
YAML
154 lines
6.4 KiB
YAML
name: Triage (needs-info)
|
|
|
|
on:
|
|
issues:
|
|
types: [opened, edited, reopened]
|
|
|
|
permissions:
|
|
issues: write
|
|
|
|
jobs:
|
|
needs_info:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Label low-context issues
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const issue_number = context.payload.issue.number;
|
|
|
|
const issue = context.payload.issue;
|
|
const title = (issue.title || "").trim();
|
|
const body = issue.body || "";
|
|
const labels = (issue.labels || []).map(l => (typeof l === "string" ? l : l.name).toLowerCase());
|
|
|
|
const NEEDS_INFO = "needs-info";
|
|
const NEEDS_INFO_COLOR = "d4c5f9";
|
|
const NEEDS_INFO_DESC = "More details needed to reproduce / triage.";
|
|
|
|
function hasLabel(name) {
|
|
return labels.includes(name.toLowerCase());
|
|
}
|
|
|
|
function extractSection(title) {
|
|
const re = new RegExp(`^###\\s+${title.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&")}\\s*$`, "m");
|
|
const match = body.match(re);
|
|
if (!match) return "";
|
|
const start = match.index + match[0].length;
|
|
const rest = body.slice(start);
|
|
const next = rest.search(/^###\s+/m);
|
|
const section = (next === -1 ? rest : rest.slice(0, next));
|
|
return section.trim();
|
|
}
|
|
|
|
function extractFirstSection(titles) {
|
|
for (const sectionTitle of titles) {
|
|
const value = extractSection(sectionTitle);
|
|
if (value) return value;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function normalizeText(value) {
|
|
return (value || "").replace(/\s+/g, " ").trim();
|
|
}
|
|
|
|
function stripIssuePrefix(value) {
|
|
return normalizeText(value).replace(/^\[[^\]]+\]:\s*/i, "").trim();
|
|
}
|
|
|
|
const steps = extractSection("Steps to reproduce");
|
|
const expected = extractSection("Expected behavior");
|
|
const actual = extractSection("Actual behavior");
|
|
const logs = extractFirstSection([
|
|
"Logs (required for crash reports)",
|
|
"Logs (optional but helpful)",
|
|
]);
|
|
const extra = extractSection("Anything else? (optional)");
|
|
const summaryTitle = stripIssuePrefix(title);
|
|
|
|
const looksLikeBugForm = !!(steps || expected || actual);
|
|
const isBugIssue = hasLabel("bug") || looksLikeBugForm;
|
|
const isFeatureIssue =
|
|
hasLabel("enhancement") ||
|
|
hasLabel("feature") ||
|
|
hasLabel("feature request");
|
|
|
|
if (!isBugIssue || isFeatureIssue) {
|
|
return;
|
|
}
|
|
|
|
const problems = [];
|
|
const genericTitle = /^(bug|issue|problem|help|question|crash|broken|error|bug report|short summary here|title here)$/i;
|
|
const numericOnlyTitle = /^#?\d+$/;
|
|
const crashPattern = /\b(crash|crashes|crashed|crashing|force close|force closes|force closed|fatal exception|app closes|app closed unexpectedly)\b/i;
|
|
const crashContext = [summaryTitle, steps, actual, extra].map(normalizeText).join("\n");
|
|
const isCrashIssue = crashPattern.test(crashContext);
|
|
const normalizedLogs = normalizeText(logs);
|
|
const hasLogs = normalizedLogs.length >= 20 && !/^(n\/a|na|none|no|not available)$/i.test(normalizedLogs);
|
|
|
|
if (!summaryTitle || summaryTitle.length < 8 || genericTitle.test(summaryTitle) || numericOnlyTitle.test(summaryTitle)) {
|
|
problems.push("Issue title (replace the default `[Bug]:` prefix with a short summary of the actual problem)");
|
|
}
|
|
if (!steps || steps.length < 30) problems.push("Steps to reproduce (please list exact steps)");
|
|
if (!expected || expected.length < 10) problems.push("Expected behavior");
|
|
if (!actual || actual.length < 10) problems.push("Actual behavior (include any on-screen error text)");
|
|
if (isCrashIssue && !hasLogs) {
|
|
problems.push("Logs (required for crash reports; include a log snippet or stack trace)");
|
|
}
|
|
|
|
async function ensureLabel(name, color, description) {
|
|
try {
|
|
await github.rest.issues.getLabel({ owner, repo, name });
|
|
} catch (e) {
|
|
try {
|
|
await github.rest.issues.createLabel({ owner, repo, name, color, description });
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
const hasNeedsInfo = hasLabel(NEEDS_INFO);
|
|
|
|
if (problems.length > 0) {
|
|
await ensureLabel(NEEDS_INFO, NEEDS_INFO_COLOR, NEEDS_INFO_DESC);
|
|
if (!hasNeedsInfo) {
|
|
await github.rest.issues.addLabels({
|
|
owner,
|
|
repo,
|
|
issue_number,
|
|
labels: [NEEDS_INFO],
|
|
});
|
|
}
|
|
|
|
const marker = "<!-- nuvio-bot:needs-info -->";
|
|
const commentBody =
|
|
`${marker}\n` +
|
|
`Thanks for the report. Could you add a bit more detail so we can reproduce it?\n\n` +
|
|
`Missing / too short:\n` +
|
|
problems.map(p => `- ${p}`).join("\n") +
|
|
`\n\n` +
|
|
`Use a specific title, for example: \`[Bug]: Playback freezes when switching audio tracks on iOS\`.\n` +
|
|
`${isCrashIssue ? `Crash reports must include logs.\n` : `Logs are optional for most issues, but they help a lot.\n`}`;
|
|
|
|
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
owner,
|
|
repo,
|
|
issue_number,
|
|
per_page: 100,
|
|
});
|
|
const alreadyCommented = comments.some(c => (c.body || "").includes(marker));
|
|
if (!alreadyCommented) {
|
|
await github.rest.issues.createComment({
|
|
owner,
|
|
repo,
|
|
issue_number,
|
|
body: commentBody,
|
|
});
|
|
}
|
|
} else if (hasNeedsInfo) {
|
|
try {
|
|
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: NEEDS_INFO });
|
|
} catch (_) {}
|
|
}
|