mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Add automated workflow for tagging aging bugs (#39284)
Automates bug triage by tagging issues based on age: `~old bug` for bugs ≥180 days, `~aging bug` for bugs ≥90 days. Relates to #39155. ## Changes **New workflow: `.github/workflows/tag-aging-bugs.yml`** - Runs daily at 8:06 UTC via cron, supports manual dispatch - Dry run mode (default: false) logs actions without modifying labels - Two-pass processing: 1. Bugs ≥180 days: adds `~old bug`, removes `~aging bug` if present 2. Bugs ≥90 days without either label: adds `~aging bug` - Uses github-script with pagination for scalability - Follows repo patterns (harden-runner, proper permissions) # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) ## Testing - [x] QA'd all new/changed functionality manually <!-- START COPILOT CODING AGENT SUFFIX --> <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > Add a GitHub Actions workflow that runs daily at 8:06am UTC, and can be manually dispatched. In that workflow, retrieve all issues labelled `bug` created >= 180 days ago that don't include the `~old bug` tag, then for each bug add the `~old bug` tag and remove `~aging bug` if it is applied. Then retrieve all issues labelled `bug` created >= 90 days ago that has neither `~aging bug` nor `~old bug` tags, and add the `~aging bug` tag. Include a dry run workflow parameter, default off, that logs rather than setting the label. </details> <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: iansltx <472804+iansltx@users.noreply.github.com>
This commit is contained in:
parent
9ce3182726
commit
b7440e8d7f
1 changed files with 189 additions and 0 deletions
189
.github/workflows/tag-aging-bugs.yml
vendored
Normal file
189
.github/workflows/tag-aging-bugs.yml
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
name: Tag aging bugs
|
||||
|
||||
# This action will tag bugs based on their age:
|
||||
# - Bugs >= 180 days old get tagged with ~old bug (and ~aging bug is removed)
|
||||
# - Bugs >= 90 days old (but < 180 days) get tagged with ~aging bug
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Daily at 8:06am UTC
|
||||
- cron: "6 8 * * *"
|
||||
workflow_dispatch: # Manual
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Dry run mode (log only, do not modify labels)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# This allows a subsequently queued workflow run to interrupt previous runs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id}}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
tag-bugs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Tag aging bugs
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const dryRun = ${{ github.event.inputs.dry_run || false }};
|
||||
console.log(`Dry run mode: ${dryRun}`);
|
||||
|
||||
// Calculate date thresholds
|
||||
const now = new Date();
|
||||
const oldBugDate = new Date(now);
|
||||
oldBugDate.setDate(oldBugDate.getDate() - 180);
|
||||
const agingBugDate = new Date(now);
|
||||
agingBugDate.setDate(agingBugDate.getDate() - 90);
|
||||
|
||||
console.log(`Old bug threshold: ${oldBugDate.toISOString()}`);
|
||||
console.log(`Aging bug threshold: ${agingBugDate.toISOString()}`);
|
||||
|
||||
// Process old bugs (>= 180 days)
|
||||
console.log('\n=== Processing old bugs (>= 180 days) ===');
|
||||
let page = 1;
|
||||
let oldBugsProcessed = 0;
|
||||
|
||||
while (true) {
|
||||
const { data: oldBugs } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: 'bug',
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
page: page,
|
||||
sort: 'created',
|
||||
direction: 'asc'
|
||||
});
|
||||
|
||||
if (oldBugs.length === 0) break;
|
||||
|
||||
for (const issue of oldBugs) {
|
||||
const createdDate = new Date(issue.created_at);
|
||||
|
||||
// Stop if we've passed the old bug threshold
|
||||
if (createdDate > oldBugDate) {
|
||||
page = Infinity; // Signal to stop pagination
|
||||
break;
|
||||
}
|
||||
|
||||
const labels = issue.labels.map(label => label.name);
|
||||
const hasOldBugLabel = labels.includes('~old bug');
|
||||
|
||||
if (!hasOldBugLabel) {
|
||||
oldBugsProcessed++;
|
||||
console.log(`Issue #${issue.number}: Created ${createdDate.toISOString()}`);
|
||||
|
||||
if (dryRun) {
|
||||
console.log(` [DRY RUN] Would add ~old bug label`);
|
||||
if (labels.includes('~aging bug')) {
|
||||
console.log(` [DRY RUN] Would remove ~aging bug label`);
|
||||
}
|
||||
} else {
|
||||
// Add ~old bug label
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: ['~old bug']
|
||||
});
|
||||
console.log(` Added ~old bug label`);
|
||||
|
||||
// Remove ~aging bug if present
|
||||
if (labels.includes('~aging bug')) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
name: '~aging bug'
|
||||
});
|
||||
console.log(` Removed ~aging bug label`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (page === Infinity) break;
|
||||
page++;
|
||||
}
|
||||
|
||||
console.log(`\nProcessed ${oldBugsProcessed} old bugs`);
|
||||
|
||||
// Process aging bugs (>= 90 days but < 180 days)
|
||||
console.log('\n=== Processing aging bugs (>= 90 days) ===');
|
||||
page = 1;
|
||||
let agingBugsProcessed = 0;
|
||||
|
||||
while (true) {
|
||||
const { data: agingBugs } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: 'bug',
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
page: page,
|
||||
sort: 'created',
|
||||
direction: 'asc'
|
||||
});
|
||||
|
||||
if (agingBugs.length === 0) break;
|
||||
|
||||
for (const issue of agingBugs) {
|
||||
const createdDate = new Date(issue.created_at);
|
||||
|
||||
// Skip if newer than aging threshold
|
||||
if (createdDate > agingBugDate) {
|
||||
page = Infinity; // Signal to stop pagination
|
||||
break;
|
||||
}
|
||||
|
||||
const labels = issue.labels.map(label => label.name);
|
||||
const hasAgingBugLabel = labels.includes('~aging bug');
|
||||
const hasOldBugLabel = labels.includes('~old bug');
|
||||
|
||||
// Only tag if it doesn't have either label
|
||||
// (hasOldBugLabel check handles issues that became old since last run)
|
||||
if (!hasAgingBugLabel && !hasOldBugLabel) {
|
||||
agingBugsProcessed++;
|
||||
console.log(`Issue #${issue.number}: Created ${createdDate.toISOString()}`);
|
||||
|
||||
if (dryRun) {
|
||||
console.log(` [DRY RUN] Would add ~aging bug label`);
|
||||
} else {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: ['~aging bug']
|
||||
});
|
||||
console.log(` Added ~aging bug label`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (page === Infinity) break;
|
||||
page++;
|
||||
}
|
||||
|
||||
console.log(`\nProcessed ${agingBugsProcessed} aging bugs`);
|
||||
console.log(`\n=== Summary ===`);
|
||||
console.log(`Total old bugs tagged: ${oldBugsProcessed}`);
|
||||
console.log(`Total aging bugs tagged: ${agingBugsProcessed}`);
|
||||
Loading…
Reference in a new issue