Skip to content

Commit 7871a51

Browse files
authored
Swapping to call shared workflow
1 parent c898a9e commit 7871a51

File tree

1 file changed

+13
-144
lines changed

1 file changed

+13
-144
lines changed
Lines changed: 13 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,23 @@
1-
name: Auto-unassign stale PR assignees
2-
1+
name: Unassign stale PR assignees
32
on:
43
schedule:
5-
- cron: "*/15 * * * *" # run every 15 minutes
4+
- cron: "*/15 * * * *"
65
workflow_dispatch:
76
inputs:
8-
enabled:
9-
description: "Enable this automation"
10-
type: boolean
11-
default: true
12-
max_age_minutes:
13-
description: "Unassign if assigned longer than X minutes"
14-
type: number
15-
default: 60
16-
dry_run:
17-
description: "Preview only; do not change assignees"
18-
type: boolean
19-
default: false
7+
enabled: { type: boolean, default: true }
8+
max_age_minutes: { type: number, default: 60 }
9+
dry_run: { type: boolean, default: false }
2010

2111
permissions:
2212
pull-requests: write
2313
issues: write
24-
25-
env:
26-
# Defaults (can be overridden via workflow_dispatch inputs)
27-
ENABLED: "true"
28-
MAX_ASSIGN_AGE_MINUTES: "60"
29-
DRY_RUN: "false"
14+
contents: read
3015

3116
jobs:
32-
sweep:
33-
runs-on: ubuntu-latest
34-
steps:
35-
- name: Resolve inputs into env
36-
run: |
37-
# Prefer manual run inputs when present
38-
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
39-
echo "ENABLED=${{ inputs.enabled }}" >> $GITHUB_ENV
40-
echo "MAX_ASSIGN_AGE_MINUTES=${{ inputs.max_age_minutes }}" >> $GITHUB_ENV
41-
echo "DRY_RUN=${{ inputs.dry_run }}" >> $GITHUB_ENV
42-
fi
43-
echo "Effective config: ENABLED=$ENABLED, MAX_ASSIGN_AGE_MINUTES=$MAX_ASSIGN_AGE_MINUTES, DRY_RUN=$DRY_RUN"
44-
45-
- name: Exit if disabled
46-
if: ${{ env.ENABLED != 'true' && env.ENABLED != 'True' && env.ENABLED != 'TRUE' }}
47-
run: echo "Disabled via ENABLED=$ENABLED. Exiting." && exit 0
48-
49-
- name: Unassign stale assignees
50-
uses: actions/github-script@v7
51-
with:
52-
script: |
53-
const owner = context.repo.owner;
54-
const repo = context.repo.repo;
55-
56-
const MAX_MIN = parseInt(process.env.MAX_ASSIGN_AGE_MINUTES || "60", 10);
57-
const DRY_RUN = ["true","True","TRUE","1","yes"].includes(String(process.env.DRY_RUN));
58-
const now = new Date();
59-
60-
core.info(`Scanning open PRs. Threshold = ${MAX_MIN} minutes. DRY_RUN=${DRY_RUN}`);
61-
62-
// List all open PRs
63-
const prs = await github.paginate(github.rest.pulls.list, {
64-
owner, repo, state: "open", per_page: 100
65-
});
66-
67-
let totalUnassigned = 0;
68-
69-
for (const pr of prs) {
70-
if (!pr.assignees || pr.assignees.length === 0) continue;
71-
72-
const number = pr.number;
73-
core.info(`PR #${number}: "${pr.title}" — assignees: ${pr.assignees.map(a => a.login).join(", ")}`);
74-
75-
// Pull reviews (to see if an assignee started a review)
76-
const reviews = await github.paginate(github.rest.pulls.listReviews, {
77-
owner, repo, pull_number: number, per_page: 100
78-
});
79-
80-
// Issue comments (general comments)
81-
const issueComments = await github.paginate(github.rest.issues.listComments, {
82-
owner, repo, issue_number: number, per_page: 100
83-
});
84-
85-
// Review comments (file-level)
86-
const reviewComments = await github.paginate(github.rest.pulls.listReviewComments, {
87-
owner, repo, pull_number: number, per_page: 100
88-
});
89-
90-
// Issue events (to find assignment timestamps)
91-
const issueEvents = await github.paginate(github.rest.issues.listEvents, {
92-
owner, repo, issue_number: number, per_page: 100
93-
});
94-
95-
for (const a of pr.assignees) {
96-
const assignee = a.login;
97-
98-
// Find the most recent "assigned" event for this assignee
99-
const assignedEvents = issueEvents
100-
.filter(e => e.event === "assigned" && e.assignee && e.assignee.login === assignee)
101-
.sort((x, y) => new Date(y.created_at) - new Date(x.created_at));
102-
103-
if (assignedEvents.length === 0) {
104-
core.info(` - @${assignee}: no 'assigned' event found; skipping.`);
105-
continue;
106-
}
107-
108-
const assignedAt = new Date(assignedEvents[0].created_at);
109-
const ageMin = (now - assignedAt) / 60000;
110-
111-
// Has the assignee commented (issue or review comments) or reviewed?
112-
const hasIssueComment = issueComments.some(c => c.user?.login === assignee);
113-
const hasReviewComment = reviewComments.some(c => c.user?.login === assignee);
114-
const hasReview = reviews.some(r => r.user?.login === assignee);
115-
116-
const eligible =
117-
ageMin >= MAX_MIN &&
118-
!hasIssueComment &&
119-
!hasReviewComment &&
120-
!hasReview &&
121-
pr.state === "open";
122-
123-
core.info(` - @${assignee}: assigned ${ageMin.toFixed(1)} min ago; commented=${hasIssueComment || hasReviewComment}; reviewed=${hasReview}; open=${pr.state==='open'} => ${eligible ? 'ELIGIBLE' : 'skip'}`);
124-
125-
if (!eligible) continue;
126-
127-
if (DRY_RUN) {
128-
core.notice(`Would unassign @${assignee} from PR #${number}`);
129-
} else {
130-
try {
131-
await github.rest.issues.removeAssignees({
132-
owner, repo, issue_number: number, assignees: [assignee]
133-
});
134-
totalUnassigned += 1;
135-
// Optional: leave a gentle heads-up comment
136-
await github.rest.issues.createComment({
137-
owner, repo, issue_number: number,
138-
body: `👋 Unassigning @${assignee} due to inactivity (> ${MAX_MIN} min without comments/reviews). This PR remains open for other reviewers.`
139-
});
140-
core.info(` Unassigned @${assignee} from #${number}`);
141-
} catch (err) {
142-
core.warning(` Failed to unassign @${assignee} from #${number}: ${err.message}`);
143-
}
144-
}
145-
}
146-
}
147-
148-
core.summary
149-
.addHeading('Auto-unassign report')
150-
.addRaw(`Threshold: ${MAX_MIN} minutes\n\n`)
151-
.addRaw(`Total unassignments: ${totalUnassigned}\n`)
152-
.write();
153-
154-
result-encoding: string
17+
call:
18+
uses: ServiceNowDevProgram/Hacktoberfest/.github/workflows/unassign-stale.yml@v1
19+
with:
20+
enabled: ${{ inputs.enabled }}
21+
max_age_minutes: ${{ inputs.max_age_minutes }}
22+
dry_run: ${{ inputs.dry_run }}
23+
secrets: inherit

0 commit comments

Comments
 (0)