From 8aeb6b2741a98debfd04af77a6c3c18b4c1fb271 Mon Sep 17 00:00:00 2001 From: Arun Chander Date: Thu, 28 Mar 2024 17:34:43 -0700 Subject: [PATCH] Add github actions for issue health --- .../workflows/close-stale-needs-more-info.yml | 78 ++++++++++++++++++ .github/workflows/manual.yml | 82 +++++++++++++++++++ .github/workflows/remove-needs-more-info.yml | 76 +++++++++++++++++ .github/workflows/stale.yml | 76 +++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 .github/workflows/close-stale-needs-more-info.yml create mode 100644 .github/workflows/manual.yml create mode 100644 .github/workflows/remove-needs-more-info.yml create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/close-stale-needs-more-info.yml b/.github/workflows/close-stale-needs-more-info.yml new file mode 100644 index 000000000..5680451be --- /dev/null +++ b/.github/workflows/close-stale-needs-more-info.yml @@ -0,0 +1,78 @@ +name: 'Close Issues with Stale Needs More Info Label' + +on: + schedule: + # Run this workflow at 00:00 every day + - cron: '0 0 * * *' + workflow_dispatch: + +permissions: + issues: write + contents: read + +jobs: + close-stale-issues: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Close Stale Issues with Needs More Info Label and Comment + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const labelName = 'needs-more-info'; + const staleDays = 14; + const closingComment = "This issue has been automatically closed due to inactivity from original bug filer and having the 'needs-more-info' label for more than 14 days. If the issue still persists, please reopen the issue with the requested information."; + const currentDate = new Date(); + const issues = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + labels: labelName, + state: 'open', + }); + + for (const issue of issues) { + let labelAddedDate = null; + + // Fetch events to find when the label was added + const events = await github.paginate(github.rest.issues.listEvents, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + }); + + // Look for the event where the label was added and capture the date + for (const event of events) { + if (event.event === 'labeled' && event.label.name === labelName) { + labelAddedDate = new Date(event.created_at); + break; + } + } + + if (labelAddedDate) { + const diffTime = Math.abs(currentDate - labelAddedDate); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + if (diffDays > staleDays) { + // Post a closing comment before closing the issue + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: closingComment, + }); + + // Close the issue + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed', + }); + + console.log(`Issue #${issue.number} has been closed with a comment as it has had the '${labelName}' label for more than ${staleDays} days.`); + } + } + } diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml new file mode 100644 index 000000000..2cb92bf2f --- /dev/null +++ b/.github/workflows/manual.yml @@ -0,0 +1,82 @@ +name: 'Manual Cleanup: Remove Needs More Info' + +on: + workflow_dispatch: + +permissions: + issues: write + contents: read + +jobs: + remove-label-on-response: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Remove Needs More Info Label if Issue Creator Responded + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const labelName = 'needs-more-info'; + const commentBody = "The `needs-more-info` label has been removed since the original bug filer has responded since the label was originally set."; + const issues = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + labels: labelName, + state: 'open', + }); + + for (const issue of issues) { + let labelAddedTimestamp = 0; + + // Fetch events to find when the label was added + const events = await github.paginate(github.rest.issues.listEventsForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + }); + + // Determine when the label was added + for (const event of events) { + if (event.event === 'labeled' && event.label.name === labelName) { + labelAddedTimestamp = new Date(event.created_at).getTime(); + break; + } + } + + if (labelAddedTimestamp > 0) { + // Check for comments by the issue creator after the label was added + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + }); + + const hasResponseAfterLabel = comments.some(comment => { + return new Date(comment.created_at).getTime() > labelAddedTimestamp && + comment.user.login === issue.user.login; + }); + + if (hasResponseAfterLabel) { + // Remove the label + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: labelName, + }); + + // Add a comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: commentBody, + }); + + console.log(`Removed "${labelName}" label from issue #${issue.number} and added a comment.`); + } + } + } diff --git a/.github/workflows/remove-needs-more-info.yml b/.github/workflows/remove-needs-more-info.yml new file mode 100644 index 000000000..5f491a275 --- /dev/null +++ b/.github/workflows/remove-needs-more-info.yml @@ -0,0 +1,76 @@ +name: 'Remove Needs More Info Label' + +on: + issue_comment: + types: [created] + +permissions: + issues: write + contents: read + +jobs: + debug-and-remove-label: + runs-on: ubuntu-latest + if: github.event.issue.pull_request == null # Ensure it runs only for issue comments + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Remove "needs-more-info" Label and Comment + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const issue = context.payload.issue; + const user = context.payload.sender; + + // Check if the comment was made by the issue creator + if (issue.user.login === user.login) { + const issueNumber = issue.number; + const labelName = 'needs-more-info'; + let labelAddedByUser = ''; + + // Find who added the 'needs-more-info' label + for await (const response of github.paginate.iterator(github.rest.issues.listEvents, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + })) { + const events = response.data; + const labelEvent = events.find(event => event.event === 'labeled' && event.label.name === labelName); + if (labelEvent) { + labelAddedByUser = labelEvent.actor.login; + break; // Stop at the most recent event that added the label + } + } + + // Get all labels for the issue + const { data: labels } = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + }); + + // Check if the issue has the specific label + if (labels.some(label => label.name === labelName)) { + // Remove the label + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + name: labelName, + }); + + // If someone added the label, comment and tag that user + if (labelAddedByUser) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: `@${labelAddedByUser}, the '${labelName}' label has been removed upon receiving further response from the original bug filer.`, + }); + } + + console.log(`Label "${labelName}" removed from issue #${issueNumber}.`); + } + } diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..fae0cc500 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,76 @@ +name: Comment on stale issues + +on: + schedule: + - cron: '0 0 * * *' # This will run daily at midnight + workflow_dispatch: + +jobs: + comment-stale-issues: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + env: + LABEL_OWNER_MAPPING_JSON: > + { + "Debugger": "@wardengnaw", + "Debugger-ExpressionEvaluation": "@wardengnaw", + "Debugger-FixedPendingInsertion": "@wardengnaw", + "Debugger-Install": "@wardengnaw", + "Debugger-Poachable": "@wardengnaw", + "Documentation": "@mikadumont", + "Project System": "@jasonmalinowski", + "Question": "@mikadumont", + "Razor": "@phil-allen-msft", + "Unity": "@jbevain" + } + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Comment on stale issues + run: | + LABEL_OWNER_MAPPING=$(echo "${LABEL_OWNER_MAPPING_JSON}") + STALE_DATE=$(date -d '14 days ago' +%Y-%m-%d) + QUERY="repo:dotnet/vscode-csharp+is:open+is:issue+updated:<${STALE_DATE}+-label:Feature%20Request+-label:Needs%20More%20Info+-label:OmniSharp+-label:Suggestion" + ISSUES=$(gh api search/issues?q="${QUERY}" --paginate --jq '.items[] | {number: .number, labels: [.labels[].name], assignees: [.assignees[].login]}') + + # Process each issue + echo "${ISSUES}" | jq -c '.' | while read -r issue; do + issue_number=$(echo "$issue" | jq '.number') + ASSIGNEES=$(echo "$issue" | jq -r '.assignees[].login') + + # Initialize TAGGED_OWNERS + TAGGED_OWNERS="" + + # Check if there are any assignees + if [[ ! -z "$ASSIGNEES" ]]; then + for assignee in $ASSIGNEES; do + TAGGED_OWNERS="$TAGGED_OWNERS @$assignee" + done + else + # If no assignees, check for label owners + LABELS=$(echo "$issue" | jq -r '.labels[].name') + for label in $LABELS; do + OWNER=$(echo $LABEL_OWNER_MAPPING | jq -r ".[\"$label\"] // empty") + if [ ! -z "$OWNER" ]; then + # Add the found owner to the list of owners to be tagged + TAGGED_OWNERS="$TAGGED_OWNERS $OWNER" + fi + done + if [ -z "$TAGGED_OWNERS" ]; then + # If no owners found in the mapping, use default owners + TAGGED_OWNERS="@dibarbet @mikadumont" + fi + fi + + # Remove leading whitespace and duplicate spaces + TAGGED_OWNERS=$(echo $TAGGED_OWNERS | xargs) + + # Comment on the issue and tag the collected owners + COMMENT="This issue has been marked as stale after 14 days of inactivity. $TAGGED_OWNERS, could you please take a look?" + gh issue comment $issue_number --body "$COMMENT" + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}