Skip to content

Commit b012dab

Browse files
Samuel FialkaSamuel Fialka
authored andcommitted
feat(ci): migrate issue to discusion
1 parent b760d45 commit b012dab

File tree

2 files changed

+133
-56
lines changed

2 files changed

+133
-56
lines changed

.github/scripts/backlog-cleanup.js

Lines changed: 129 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* exempt labels that have been inactive for 90+ days.
1111
* - Avoids sending duplicate Friendly Reminder comments if one was
1212
* posted within the last 7 days.
13+
* - Moves issues labeled 'questions' to GitHub Discussions
1314
*/
1415

1516
const dedent = (strings, ...values) => {
@@ -28,68 +29,113 @@ async function fetchAllOpenIssues(github, owner, repo) {
2829
let page = 1;
2930

3031
while (true) {
31-
const response = await github.rest.issues.listForRepo({
32+
try {
33+
const response = await github.rest.issues.listForRepo({
34+
owner,
35+
repo,
36+
state: 'open',
37+
per_page: 100,
38+
page,
39+
});
40+
const data = response.data || [];
41+
if (data.length === 0) break;
42+
const onlyIssues = data.filter(issue => !issue.pull_request);
43+
issues.push(...onlyIssues);
44+
if (data.length < 100) break;
45+
page++;
46+
} catch (err) {
47+
console.error('Error fetching issues:', err);
48+
break;
49+
}
50+
}
51+
return issues;
52+
}
53+
54+
55+
async function migrateToDiscussion(github, owner, repo, issue, categories) {
56+
const discussionCategory = 'Q&A';
57+
try {
58+
const category = categories.find(cat =>
59+
cat.name.toLowerCase() === discussionCategory.toLowerCase()
60+
);
61+
if (!category) {
62+
throw new Error(`Discussion category '${discussionCategory}' not found.`);
63+
}
64+
const { data: discussion } = await github.rest.discussions.create({
3265
owner,
3366
repo,
34-
state: 'open',
35-
per_page: 100,
36-
page,
67+
title: issue.title,
68+
body: `Originally created by @${issue.user.login} in #${issue.number}\n\n---\n\n${issue.body || ''}`,
69+
category_id: category.id,
3770
});
38-
39-
const data = response.data || [];
40-
if (data.length === 0) break;
41-
const onlyIssues = data.filter(issue => !issue.pull_request);
42-
issues.push(...onlyIssues);
43-
44-
if (data.length < 100) break;
45-
page++;
71+
await github.rest.issues.createComment({
72+
owner,
73+
repo,
74+
issue_number: issue.number,
75+
body: `💬 This issue was moved to [Discussions](${discussion.html_url}) for better visibility.`,
76+
});
77+
await github.rest.issues.update({
78+
owner,
79+
repo,
80+
issue_number: issue.number,
81+
state: 'closed',
82+
});
83+
return discussion.html_url;
84+
} catch (err) {
85+
console.error(`Error migrating issue #${issue.number} to discussion:`, err);
86+
return null;
4687
}
47-
return issues;
4888
}
4989

90+
5091
const shouldSendReminder = (issue, exemptLabels, closeLabels) => {
51-
const hasExempt = issue.labels.some(l => exemptLabels.includes(l.name));
52-
const hasClose = issue.labels.some(l => closeLabels.includes(l.name));
53-
return issue.assignees.length > 0 && !hasExempt && !hasClose;
92+
const hasExempt = issue.labels.some(l => exemptLabels.includes(l.name));
93+
const hasClose = issue.labels.some(l => closeLabels.includes(l.name));
94+
return issue.assignees.length > 0 && !hasExempt && !hasClose;
5495
};
5596

5697

5798
module.exports = async ({ github, context }) => {
58-
const { owner, repo } = context.repo;
59-
const issues = await fetchAllOpenIssues(github, owner, repo);
6099
const now = new Date();
61100
const thresholdDays = 90;
62-
const exemptLabels = ['to-be-discussed'];
63-
const closeLabels = ['awaiting-response'];
64-
const sevenDays = 7 * 24 * 60 * 60 * 1000;
101+
const exemptLabels = ['Status: Community help needed', 'Status: Needs investigation'];
102+
const closeLabels = ['Status: Awaiting Response'];
103+
const discussionLabel = 'Type: Question';
104+
const { owner, repo } = context.repo;
65105

66106
let totalClosed = 0;
67107
let totalReminders = 0;
68108
let totalSkipped = 0;
109+
let totalMigrated = 0;
110+
let issues = [];
111+
let categories = [];
112+
113+
try {
114+
issues = await fetchAllOpenIssues(github, owner, repo);
115+
} catch (err) {
116+
console.error('Failed to fetch issues:', err);
117+
return;
118+
}
119+
120+
try {
121+
const { data } = await github.rest.discussions.listCategories({ owner, repo });
122+
categories = data;
123+
} catch (err) {
124+
console.error('Error fetching discussion categories:', err);
125+
}
69126

70127
for (const issue of issues) {
71128
const isAssigned = issue.assignees && issue.assignees.length > 0;
72129
const lastUpdate = new Date(issue.updated_at);
73130
const daysSinceUpdate = Math.floor((now - lastUpdate) / (1000 * 60 * 60 * 24));
74131

75-
if (daysSinceUpdate < thresholdDays) {
76-
totalSkipped++;
132+
if (issue.labels.some(label => label.name === discussionLabel)) {
133+
const migrated = await migrateToDiscussion(github, owner, repo, issue, categories);
134+
if (migrated) totalMigrated++;
77135
continue;
78136
}
79137

80-
const { data: comments } = await github.rest.issues.listComments({
81-
owner,
82-
repo,
83-
issue_number: issue.number,
84-
per_page: 10,
85-
});
86-
87-
const recentFriendlyReminder = comments.find(comment =>
88-
comment.user.login === 'github-actions[bot]' &&
89-
comment.body.includes('⏰ Friendly Reminder') &&
90-
(now - new Date(comment.created_at)) < sevenDays
91-
);
92-
if (recentFriendlyReminder) {
138+
if (daysSinceUpdate < thresholdDays) {
93139
totalSkipped++;
94140
continue;
95141
}
@@ -100,19 +146,45 @@ module.exports = async ({ github, context }) => {
100146
}
101147

102148
if (issue.labels.some(label => closeLabels.includes(label.name)) || !isAssigned) {
103-
await github.rest.issues.createComment({
104-
owner,
105-
repo,
106-
issue_number: issue.number,
107-
body: '⚠️ This issue was closed automatically due to inactivity. Please reopen or open a new one if still relevant.',
108-
});
109-
await github.rest.issues.update({
149+
try {
150+
await github.rest.issues.createComment({
151+
owner,
152+
repo,
153+
issue_number: issue.number,
154+
body: '⚠️ This issue was closed automatically due to inactivity. Please reopen or open a new one if still relevant.',
155+
});
156+
await github.rest.issues.update({
157+
owner,
158+
repo,
159+
issue_number: issue.number,
160+
state: 'closed',
161+
});
162+
totalClosed++;
163+
} catch (err) {
164+
console.error(`Error closing issue #${issue.number}:`, err);
165+
}
166+
continue;
167+
}
168+
169+
let comments = [];
170+
try {
171+
const { data } = await github.rest.issues.listComments({
110172
owner,
111173
repo,
112174
issue_number: issue.number,
113-
state: 'closed',
175+
per_page: 50,
114176
});
115-
totalClosed++;
177+
comments = data;
178+
} catch (err) {
179+
console.error(`Error fetching comments for issue #${issue.number}:`, err);
180+
}
181+
182+
const recentFriendlyReminder = comments.find(comment =>
183+
comment.user.login === 'github-actions[bot]' &&
184+
comment.body.includes('⏰ Friendly Reminder')
185+
);
186+
if (recentFriendlyReminder) {
187+
totalSkipped++;
116188
continue;
117189
}
118190

@@ -129,14 +201,17 @@ module.exports = async ({ github, context }) => {
129201
- Or label it 'awaiting-response' if you're waiting on something
130202
131203
This is just a reminder; the issue remains open for now.`;
132-
133-
await github.rest.issues.createComment({
134-
owner,
135-
repo,
136-
issue_number: issue.number,
137-
body: comment,
138-
});
139-
totalReminders++;
204+
try {
205+
await github.rest.issues.createComment({
206+
owner,
207+
repo,
208+
issue_number: issue.number,
209+
body: comment,
210+
});
211+
totalReminders++;
212+
} catch (err) {
213+
console.error(`Error sending reminder for issue #${issue.number}:`, err);
214+
}
140215
}
141216
}
142217

@@ -145,5 +220,6 @@ module.exports = async ({ github, context }) => {
145220
Total issues processed: ${issues.length}
146221
Total issues closed: ${totalClosed}
147222
Total reminders sent: ${totalReminders}
223+
Total migrated to discussions: ${totalMigrated}
148224
Total skipped: ${totalSkipped}`);
149225
};

.github/workflows/backlog-bot.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ name: "Backlog Management Bot"
22

33
on:
44
schedule:
5-
- cron: '0 2 * * *' # Run daily at 2 AM UTC
5+
- cron: '0 4 * * *' # Run daily at 4 AM UTC
66

77
permissions:
88
issues: write
9+
discussions: write
910
contents: read
1011

1112
jobs:
@@ -14,10 +15,10 @@ jobs:
1415
runs-on: ubuntu-latest
1516
steps:
1617
- name: Checkout repository
17-
uses: actions/checkout@v4
18+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
1819

1920
- name: Run backlog cleanup script
20-
uses: actions/github-script@v7
21+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
2122
with:
2223
github-token: ${{ secrets.GITHUB_TOKEN }}
2324
script: |

0 commit comments

Comments
 (0)