Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit c136a86

Browse files
Samuel FialkaSamuel Fialka
Samuel Fialka
authored and
Samuel Fialka
committed
feat(ci): migrate issue to discusion
1 parent b760d45 commit c136a86

File tree

2 files changed

+130
-54
lines changed

2 files changed

+130
-54
lines changed

‎.github/scripts/backlog-cleanup.js‎

Lines changed: 126 additions & 51 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,112 @@ 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 }) => {
99+
let categories = [];
100+
try {
101+
const { data } = await github.rest.discussions.listCategories({ owner, repo });
102+
categories = data;
103+
} catch (err) {
104+
console.error('Error fetching discussion categories:', err);
105+
}
58106
const { owner, repo } = context.repo;
59-
const issues = await fetchAllOpenIssues(github, owner, repo);
107+
let issues = [];
108+
try {
109+
issues = await fetchAllOpenIssues(github, owner, repo);
110+
} catch (err) {
111+
console.error('Failed to fetch issues:', err);
112+
return;
113+
}
60114
const now = new Date();
61115
const thresholdDays = 90;
62-
const exemptLabels = ['to-be-discussed'];
63-
const closeLabels = ['awaiting-response'];
116+
const exemptLabels = ['Status: Community help needed', 'Status: Needs investigation'];
117+
const closeLabels = ['Status: Awaiting Response'];
118+
const discussionLabel = 'Type: Question';
64119
const sevenDays = 7 * 24 * 60 * 60 * 1000;
65120

66121
let totalClosed = 0;
67122
let totalReminders = 0;
68123
let totalSkipped = 0;
124+
let totalMigrated = 0;
69125

70126
for (const issue of issues) {
71127
const isAssigned = issue.assignees && issue.assignees.length > 0;
72128
const lastUpdate = new Date(issue.updated_at);
73129
const daysSinceUpdate = Math.floor((now - lastUpdate) / (1000 * 60 * 60 * 24));
74130

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

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) {
137+
if (daysSinceUpdate < thresholdDays) {
93138
totalSkipped++;
94139
continue;
95140
}
@@ -100,19 +145,45 @@ module.exports = async ({ github, context }) => {
100145
}
101146

102147
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({
148+
try {
149+
await github.rest.issues.createComment({
150+
owner,
151+
repo,
152+
issue_number: issue.number,
153+
body: '⚠️ This issue was closed automatically due to inactivity. Please reopen or open a new one if still relevant.',
154+
});
155+
await github.rest.issues.update({
156+
owner,
157+
repo,
158+
issue_number: issue.number,
159+
state: 'closed',
160+
});
161+
totalClosed++;
162+
} catch (err) {
163+
console.error(`Error closing issue #${issue.number}:`, err);
164+
}
165+
continue;
166+
}
167+
168+
let comments = [];
169+
try {
170+
const { data } = await github.rest.issues.listComments({
110171
owner,
111172
repo,
112173
issue_number: issue.number,
113-
state: 'closed',
174+
per_page: 50,
114175
});
115-
totalClosed++;
176+
comments = data;
177+
} catch (err) {
178+
console.error(`Error fetching comments for issue #${issue.number}:`, err);
179+
}
180+
181+
const recentFriendlyReminder = comments.find(comment =>
182+
comment.user.login === 'github-actions[bot]' &&
183+
comment.body.includes('⏰ Friendly Reminder')
184+
);
185+
if (recentFriendlyReminder) {
186+
totalSkipped++;
116187
continue;
117188
}
118189

@@ -129,14 +200,17 @@ module.exports = async ({ github, context }) => {
129200
- Or label it 'awaiting-response' if you're waiting on something
130201
131202
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++;
203+
try {
204+
await github.rest.issues.createComment({
205+
owner,
206+
repo,
207+
issue_number: issue.number,
208+
body: comment,
209+
});
210+
totalReminders++;
211+
} catch (err) {
212+
console.error(`Error sending reminder for issue #${issue.number}:`, err);
213+
}
140214
}
141215
}
142216

@@ -145,5 +219,6 @@ module.exports = async ({ github, context }) => {
145219
Total issues processed: ${issues.length}
146220
Total issues closed: ${totalClosed}
147221
Total reminders sent: ${totalReminders}
222+
Total migrated to discussions: ${totalMigrated}
148223
Total skipped: ${totalSkipped}`);
149224
};

‎.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)

AltStyle によって変換されたページ (->オリジナル) /