From 3e90d659fa256302a1d27d91650cbe94bb044e80 Mon Sep 17 00:00:00 2001 From: csehatt741 Date: Tue, 18 Feb 2025 09:37:08 +0100 Subject: [PATCH] Issue unassign workflow created --- .github/workflows/issue-pr-reminder.yaml | 253 +++++++++++++---------- .github/workflows/issue-unassign.yaml | 120 +++++++++++ 2 files changed, 265 insertions(+), 108 deletions(-) create mode 100644 .github/workflows/issue-unassign.yaml diff --git a/.github/workflows/issue-pr-reminder.yaml b/.github/workflows/issue-pr-reminder.yaml index 11109db9..f40e9de5 100644 --- a/.github/workflows/issue-pr-reminder.yaml +++ b/.github/workflows/issue-pr-reminder.yaml @@ -1,108 +1,145 @@ -name: Issue PR Reminder - -on: - schedule: - - cron: '0 * * * *' # Runs every hour - workflow_dispatch: - -jobs: - issue-reminder: - runs-on: ubuntu-latest - steps: - - uses: actions/github-script@v7 - env: - CREATE_REMINDER_AFTER_HOURS: ${{ vars.CREATE_REMINDER_AFTER_HOURS }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const owner = "keyshade-xyz"; - const repo = "keyshade"; - const createReminderAfterHours = process.env.CREATE_REMINDER_AFTER_HOURS || 48; - const createReminderAfterMilliseconds = createReminderAfterHours * 60 * 60 * 1000; - const now = Date.now(); - - async function listOpenIssuesWithoutPR() { - const allIssues = []; - let page = 1; - let hasNextPage = true; - - while (hasNextPage) { - const issuesResponse = await github.rest.issues.listForRepo({ - owner, - repo, - state: "open", - per_page: 100, - page - }); - - const issuesWithoutPR = issuesResponse.data.filter(issue => !issue.pull_request); - - allIssues.push(...issuesWithoutPR); - - hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"'); - page++; - } - - return allIssues; - } - - async function listIssueComments(issue) { - const allComments = []; - let page = 1; - let hasNextPage = true; - - while (hasNextPage) { - const issuesResponse = await github.rest.issues.listComments({ - owner, - repo, - issue_number: issue.number, - per_page: 100, - page - }); - - allComments.push(...issuesResponse.data); - - hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"'); - page++; - } - - return allComments; - } - - async function createReminders() { - const issues = await listOpenIssuesWithoutPR(); - - for (const issue of issues) { - const issueCreatedAt = new Date(issue.created_at); - const createReminderAfter = new Date(issueCreatedAt.getTime() + createReminderAfterMilliseconds); - - // Check if it's time to create the reminder - if (now < createReminderAfter) { - continue; - } - - // Create reminder for each assignee - const comments = await listIssueComments(issue); - - for (const assignee of issue.assignees) { - const reminder = `@${assignee.login}, please open a draft PR linking this issue!`; - - // Check if the issue already has the reminder - if (comments.some(comment => comment.body === reminder)) { - continue; - } - - // Create the reminder - await github.rest.issues.createComment({ - owner, - repo, - issue_number: issue.number, - body: reminder - }); - - console.log(`Reminder created for: ${assignee.login}`); - } - } - } - - await createReminders(); \ No newline at end of file +name: Issue PR Reminder + +on: + schedule: + - cron: '0 * * * *' # Runs every hour + workflow_dispatch: + +jobs: + issue-reminder: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + env: + CREATE_PR_REMINDER_ENABLED: ${{ vars.CREATE_PR_REMINDER_ENABLED }} + CREATE_PR_REMINDER_AFTER_DAYS: ${{ vars.CREATE_PR_REMINDER_AFTER_DAYS }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const owner = "keyshade-xyz"; + const repo = "keyshade"; + const createPrReminderEnabled = process.env.CREATE_PR_REMINDER_ENABLED || false; + const createPrReminderAfterDays = process.env.CREATE_PR_REMINDER_AFTER_DAYS || 2; + const createPrReminderAfterMilliseconds = createPrReminderAfterDays * 24 * 60 * 60 * 1000; + const now = Date.now(); + + async function listOpenIssues() { + const issues = []; + let page = 1; + let hasNextPage = true; + + while (hasNextPage) { + const issuesResponse = await github.rest.issues.listForRepo({ + owner, + repo, + state: "open", + per_page: 100, + page + }); + + issues.push(...issuesResponse.data); + + hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"'); + page++; + } + + return issues; + } + + async function listIssueEvents(issue) { + const allEvents = []; + let page = 1; + let hasNextPage = true; + + while (hasNextPage) { + const issuesResponse = await github.rest.issues.listEventsForTimeline({ + owner, + repo, + issue_number: issue.number, + per_page: 100, + page + }); + + allEvents.push(...issuesResponse.data); + + hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"'); + page++; + } + + return allEvents.sort((a, b) => a.id > b.id); + } + + async function listIssueComments(issue) { + const allComments = []; + let page = 1; + let hasNextPage = true; + + while (hasNextPage) { + const issuesResponse = await github.rest.issues.listComments({ + owner, + repo, + issue_number: issue.number, + per_page: 100, + page + }); + + allComments.push(...issuesResponse.data); + + hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"'); + page++; + } + + return allComments; + } + + async function createPrReminders() { + const issues = await listOpenIssues(); + + for (const issue of issues) { + const events = await listIssueEvents(issue); + const pullRequests = events + .filter(event => event.event === 'cross-referenced') + .map(event => event.source.issue) + .filter(issue => issue && issue.pull_request); + const comments = await listIssueComments(issue); + + for (const assignee of issue.assignees) { + // Check if assignee has already opened a PR + const assigneePullRequest = pullRequests.find(pullRequest => pullRequest.user.login === assignee.login); + + if(assigneePullRequest) { + continue; + } + + // Check if it is time to create a reminder + const assignedEvent = events.find(event => event.event === 'assigned' && event.assignee.login === assignee.login); + const issueAssignedAt = new Date(assignedEvent.created_at); + const createPrReminderAfter = new Date(issueAssignedAt.getTime() + createPrReminderAfterMilliseconds); + + if (now < createPrReminderAfter) { + continue; + } + + // Check if reminder has already been created + const reminder = `@${assignee.login}, please open a draft PR linking this issue!`; + + if (comments.some(comment => comment.body === reminder)) { + continue; + } + + // Create reminder + if(createPrReminderEnabled) { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issue.number, + body: reminder + }); + } + + console.log(`Issue '${issue.number}' reminder created for: ${assignee.login}`); + } + } + } + + await createPrReminders(); \ No newline at end of file diff --git a/.github/workflows/issue-unassign.yaml b/.github/workflows/issue-unassign.yaml new file mode 100644 index 00000000..33a20f70 --- /dev/null +++ b/.github/workflows/issue-unassign.yaml @@ -0,0 +1,120 @@ +name: Issue Unassign + +on: + schedule: + - cron: '0 * * * *' # Runs every hour + workflow_dispatch: + +jobs: + issue-reminder: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + env: + UNASSIGN_ISSUE_ENABLED: ${{ vars.UNASSIGN_ISSUE_ENABLED }} + UNASSIGN_ISSUE_AFTER_DAYS: ${{ vars.UNASSIGN_ISSUE_AFTER_DAYS }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const owner = "keyshade-xyz"; + const repo = "keyshade"; + const unassignIssueEnabled = process.env.UNASSIGN_ISSUE_ENABLED || false; + const unassignIssueAfterDays = process.env.CREATE_PR_REMINDER_AFTER_DAYS || 14; + const unassignIssueAfterMilliseconds = unassignIssueAfterDays * 24 * 60 * 60 * 1000; + const now = Date.now(); + + async function listOpenIssues() { + const issues = []; + let page = 1; + let hasNextPage = true; + + while (hasNextPage) { + const issuesResponse = await github.rest.issues.listForRepo({ + owner, + repo, + state: "open", + per_page: 100, + page + }); + + issues.push(...issuesResponse.data); + + hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"'); + page++; + } + + return issues; + } + + async function listIssueEvents(issue) { + const allEvents = []; + let page = 1; + let hasNextPage = true; + + while (hasNextPage) { + const issuesResponse = await github.rest.issues.listEventsForTimeline({ + owner, + repo, + issue_number: issue.number, + per_page: 100, + page + }); + + allEvents.push(...issuesResponse.data); + + hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"'); + page++; + } + + return allEvents.sort((a, b) => a.id > b.id); + } + + async function unassignIssues() { + const issues = await listOpenIssues(); + + for (const issue of issues) { + const events = await listIssueEvents(issue); + const pullRequests = events + .filter(event => event.event === 'cross-referenced') + .map(event => event.source.issue) + .filter(issue => issue && issue.pull_request); + + for (const assignee of issue.assignees) { + // Check if assignee has already opened a PR + const assigneePullRequest = pullRequests.find(pullRequest => pullRequest.user.login === assignee.login); + + if(assigneePullRequest) { + continue; + } + + // Check if it is time to unassign issue + const assignedEvent = events.find(event => event.event === 'assigned' && event.assignee.login === assignee.login); + const issueAssignedAt = new Date(assignedEvent.created_at); + const unassignIssueAfter = new Date(issueAssignedAt.getTime() + unassignIssueAfterMilliseconds); + + if (now < unassignIssueAfter) { + continue; + } + + if(unassignIssueEnabled) { + await github.rest.issues.removeAssignees({ + owner, + repo, + issue_number: issue.number, + assignees: [assignee.login], + }); + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issue.number, + body: `Unassigned the issue from @${assignee.login}!`, + }); + } + + console.log(`Issue '${issue.number}' user unassigned: ${assignee.login}`); + } + } + } + + await unassignIssues(); \ No newline at end of file