Skip to content

Commit ebbc77c

Browse files
authored
Merge pull request #5246 from OWASP-BLT/copilot/add-days-old-label-action
Add daily workflow to label issues/PRs by last activity
2 parents f2a3633 + 5508012 commit ebbc77c

File tree

2 files changed

+170
-2
lines changed

2 files changed

+170
-2
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: Add Last Active Label
2+
3+
# Runs daily to add labels based on days since last activity
4+
# Also allows manual triggering for testing
5+
on:
6+
schedule:
7+
# Run daily at midnight UTC (00:00)
8+
- cron: '0 0 * * *'
9+
workflow_dispatch: # Allow manual triggering for testing
10+
11+
permissions:
12+
pull-requests: write
13+
issues: write
14+
15+
jobs:
16+
add_last_active_label:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Add Last Active Label to Issues and PRs
20+
uses: actions/github-script@v7
21+
with:
22+
github-token: ${{ secrets.GITHUB_TOKEN }}
23+
script: |
24+
const owner = context.repo.owner;
25+
const repo = context.repo.repo;
26+
27+
// Get all open issues (includes PRs)
28+
core.info('Fetching all open issues and pull requests...');
29+
const allItems = await github.paginate(github.rest.issues.listForRepo, {
30+
owner,
31+
repo,
32+
state: 'open',
33+
per_page: 100,
34+
});
35+
36+
core.info(`Found ${allItems.length} open items (issues and PRs)`);
37+
38+
// Calculate days since last activity and add labels
39+
const now = Date.now();
40+
const oneDayMs = 24 * 60 * 60 * 1000;
41+
42+
// Color scheme constants for different age ranges
43+
const COLOR_FRESH = '0e8a16'; // Green (0-2 days)
44+
const COLOR_GETTING_OLD = 'fbca04'; // Yellow (3-7 days)
45+
const COLOR_NEEDS_ATTENTION = 'ff9800'; // Orange (8-14 days)
46+
const COLOR_STALE = 'e74c3c'; // Red (15+ days) - project's brand color
47+
48+
// Regex pattern to match existing last-active labels (compile once)
49+
const lastActiveRegex = /^last-active:/i;
50+
51+
// Function to ensure label exists (define once, outside loop)
52+
async function ensureLabelExists(labelName, labelColor, description) {
53+
try {
54+
await github.rest.issues.getLabel({ owner, repo, name: labelName });
55+
} catch (e) {
56+
if (e.status === 404) {
57+
await github.rest.issues.createLabel({
58+
owner,
59+
repo,
60+
name: labelName,
61+
color: labelColor,
62+
description: description,
63+
});
64+
core.info(` Created label: ${labelName}`);
65+
} else {
66+
throw e;
67+
}
68+
}
69+
}
70+
71+
// Process each item
72+
for (const item of allItems) {
73+
const itemNumber = item.number;
74+
const itemType = item.pull_request ? 'PR' : 'Issue';
75+
const lastActivity = new Date(item.updated_at);
76+
const daysSinceActivity = Math.floor((now - lastActivity.getTime()) / oneDayMs);
77+
78+
core.info(`\nProcessing ${itemType} #${itemNumber}: ${item.title}`);
79+
core.info(` Last activity: ${item.updated_at}`);
80+
core.info(` Days since activity: ${daysSinceActivity}`);
81+
82+
// Determine the label based on days since last activity
83+
const newLabel = `last-active: ${daysSinceActivity}d`;
84+
85+
// Set color based on the number of days
86+
let labelColor;
87+
let description;
88+
if (daysSinceActivity <= 2) {
89+
labelColor = COLOR_FRESH;
90+
description = `${itemType} last updated ${daysSinceActivity} day${daysSinceActivity === 1 ? '' : 's'} ago`;
91+
} else if (daysSinceActivity <= 7) {
92+
labelColor = COLOR_GETTING_OLD;
93+
description = `${itemType} last updated ${daysSinceActivity} days ago`;
94+
} else if (daysSinceActivity <= 14) {
95+
labelColor = COLOR_NEEDS_ATTENTION;
96+
description = `${itemType} last updated ${daysSinceActivity} days ago`;
97+
} else {
98+
labelColor = COLOR_STALE;
99+
description = `${itemType} last updated ${daysSinceActivity} days ago`;
100+
}
101+
102+
// Get current labels on the item
103+
const { data: current } = await github.rest.issues.listLabelsOnIssue({
104+
owner,
105+
repo,
106+
issue_number: itemNumber,
107+
per_page: 100
108+
});
109+
const currentNames = new Set(current.map(l => l.name));
110+
111+
// Check if the correct label is already present
112+
if (currentNames.has(newLabel)) {
113+
core.info(` Label ${newLabel} already present - no action needed`);
114+
continue;
115+
}
116+
117+
// Remove any existing last-active labels (since we need to update)
118+
for (const name of currentNames) {
119+
if (lastActiveRegex.test(name)) {
120+
try {
121+
await github.rest.issues.removeLabel({
122+
owner,
123+
repo,
124+
issue_number: itemNumber,
125+
name
126+
});
127+
core.info(` Removed old label: ${name}`);
128+
} catch (err) {
129+
core.warning(` Failed to remove label ${name}: ${err.message}`);
130+
}
131+
}
132+
}
133+
134+
// Ensure the new label exists (create if missing)
135+
await ensureLabelExists(newLabel, labelColor, description);
136+
137+
// Add the new label
138+
await github.rest.issues.addLabels({
139+
owner,
140+
repo,
141+
issue_number: itemNumber,
142+
labels: [newLabel]
143+
});
144+
core.info(` Applied label: ${newLabel}`);
145+
}
146+
147+
core.info(`\n✅ Finished processing ${allItems.length} items`);

docs/github_actions.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ This document provides a comprehensive overview of all GitHub Actions workflows
1717

1818
## Overview
1919

20-
The OWASP BLT repository uses **27 GitHub Actions workflows** to automate nearly every aspect of the development lifecycle. These workflows help maintain high code quality, manage contributions from a global community, and ensure security best practices are followed.
20+
The OWASP BLT repository uses **28 GitHub Actions workflows** to automate nearly every aspect of the development lifecycle. These workflows help maintain high code quality, manage contributions from a global community, and ensure security best practices are followed.
2121

22-
**Total Workflows**: 27
22+
**Total Workflows**: 28
2323

2424
## Why GitHub Actions Matter in the Age of AI
2525

@@ -379,6 +379,27 @@ Workflows that handle issue lifecycle and assignment.
379379

380380
**AI Relevance**: Manages contribution flow—important when AI tools make it easy to create many issues quickly.
381381

382+
#### 5.3 Add Last Active Label (`add-last-active-label.yml`)
383+
**Purpose**: Automatically label issues and PRs based on days since last activity
384+
385+
**Triggers**:
386+
- Daily schedule (midnight UTC)
387+
- Manual dispatch for testing
388+
389+
**Key Features**:
390+
- Adds `last-active: Xd` labels to all open issues and PRs
391+
- Based on `updated_at` timestamp (last activity), not when created
392+
- Automatically removes outdated last-active labels before adding new ones
393+
- Creates labels with color-coded severity:
394+
- 0-2 days: Green (fresh)
395+
- 3-7 days: Yellow (getting old)
396+
- 8-14 days: Orange (needs attention)
397+
- 15+ days: Red (stale)
398+
- Processes both issues and pull requests
399+
- Runs daily to keep labels current
400+
401+
**AI Relevance**: Helps prioritize review and maintenance efforts by surfacing items that need attention, critical for managing high-volume AI-assisted contributions.
402+
382403
---
383404

384405
### 6. Automation & Maintenance

0 commit comments

Comments
 (0)