From 88454860d6c4985fd6dfc5a7b685e7acd4bdaa53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ord=C3=A1s?= <3125580+davorpa@users.noreply.github.com> Date: Tue, 13 Sep 2022 18:11:21 +0200 Subject: [PATCH] ci(stale): create `stale.yml` workflow (#7053) * ci(stale): create `stale.yml` workflow - Treat issues and PRs separately - At the end, generate a cross-linked summary with all processed issues/PRs * chore: apply suggested interval ratio 60/30 * chore: cron every 6 hours o'clock... at 0:00am 6:00am 12:00pm 18:00pm * chore: multiline string for messages posted by bot * fix: concurrency and job atomicity * format: remove the superfluous newlines Co-authored-by: ImVector <59611597+LuigiImVector@users.noreply.github.com> * security: limit access to contents * feat: `blocked,must,should,keep` exempt labels * fix: report not work since job outputs aren't exported from their respective jobs * chore: cron every 1 hour o'clock * fix: filter outputs to exempt share secrets between jobs * chore: use emoji text instead its icon * chore: cron back again every 6 hours o'clock... Reverts commit dc44e45b274082fcb7fec956c4528ba347765234 * chore: refactor workflow to have only one job but one step for issues and other for PRs New features: - run on push over this workflow - run manually have debug option to make a dry-run execution - run scheduled is every 6 hours at o'clock - Summary report is in table format instead of list items * chore: cron back again once a day at 00:00 UTC Co-authored-by: ImVector <59611597+LuigiImVector@users.noreply.github.com> --- .github/workflows/stale.yml | 201 ++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000000..b952e37546f01 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,201 @@ +name: Stale handler + +on: + push: # when push this files to branches.... + branches: + - 'main' + - 'gh-actions/test' + paths: + - '.github/workflows/stale.yml' # - this workflow + workflow_dispatch: # manually + inputs: + debug-only: + type: boolean + description: 'If enabled, debug mode is on and then API calls that can alter your issues will not happen' + required: true + default: true + schedule: # or + - cron: "0 0 * * *" # once a day at 00:00 o'clock + +permissions: + # no checkout/branching needed + contents: none + +# This allows a subsequently queued workflow run to interrupt/wait for previous runs +concurrency: + group: '${{ github.workflow }}' + cancel-in-progress: false # true: interrupt, false = wait for + +jobs: + stale: + name: Staler job + runs-on: ubuntu-latest + outputs: + # "XXX-len": the length of the "XXX" output object + staled-issues: ${{ steps.set-staled.outputs.issues }} + staled-issues-len: ${{ steps.set-staled.outputs.issues-len }} + staled-prs: ${{ steps.set-staled.outputs.prs }} + staled-prs-len: ${{ steps.set-staled.outputs.prs-len }} + closed-issues: ${{ steps.set-closed.outputs.issues }} + closed-issues-len: ${{ steps.set-closed.outputs.issues-len }} + closed-prs: ${{ steps.set-closed.outputs.prs }} + closed-prs-len: ${{ steps.set-closed.outputs.prs-len }} + # enable write access rights to allow bot comments and labeling + permissions: + issues: write + pull-requests: write + steps: + - name: Stale issues + uses: actions/stale@v5 + id: stale-issues + with: + debug-only: ${{ github.event.inputs.debug-only == 'true' }} + operations-per-run: 30 + days-before-stale: 60 + days-before-close: 30 + ignore-updates: false + remove-stale-when-updated: true + stale-issue-label: "stale" + close-issue-label: "stale: closed" + stale-issue-message: | + This issue has been automatically marked as stale because it has not had recent activity during last 60 days :sleeping: + + It will be closed in 30 days if no further activity occurs. To unstale this issue, remove stale label or add a comment with a detailed explanation. + + There can be many reasons why some specific issue has no activity. The most probable cause is lack of time, not lack of interest. + + Thank you for your patience :heart: + close-issue-message: | + This issue has been automatically closed because it has been inactive during the last 30 days since being marked as stale. + + As author or maintainer, it can always be reopened if you see that carry on been useful. + + Anyway, thank you for your interest in contribute :heart: + close-issue-reason: not_planned + exempt-issue-labels: "blocked,must,should,keep,:busts_in_silhouette: discussion,:eyes: Needs Review,:pushpin: pinned" + # disable PR processing at all (this step is for treat issues) + days-before-pr-stale: -1 + days-before-pr-close: -1 + ignore-pr-updates: true + remove-pr-stale-when-updated: false + stale-pr-label: " " + + - name: Print outputs for issues + run: echo ${{ join(steps.stale-issues.outputs.*, ',') }} + + - name: Stale Pull Requests + uses: actions/stale@v5 + id: stale-prs + with: + debug-only: ${{ github.event.inputs.debug-only == 'true' }} + operations-per-run: 30 + days-before-stale: 60 + days-before-close: 30 + ignore-updates: false + remove-stale-when-updated: true + stale-pr-label: "stale" + close-pr-label: "stale: closed" + stale-pr-message: | + This Pull Request has been automatically marked as stale because it has not had recent activity during last 60 days :sleeping: + + It will be closed in 30 days if no further activity occurs. To unstale this PR, draft it, remove stale label, comment with a detailed explanation or push more commits. + + There can be many reasons why some specific PR has no activity. The most probable cause is lack of time, not lack of interest. + + Thank you for your patience :heart: + close-pr-message: | + This Pull Request has been automatically closed because it has been inactive during the last 30 days since being marked as stale. + + As author or maintainer, it can always be reopened if you see that carry on been useful. + + Anyway, thank you for your interest in contribute :heart: + exempt-draft-pr: true + exempt-pr-labels: "blocked,must,should,keep,:busts_in_silhouette: discussion,:eyes: Needs Review" + delete-branch: false # if true, job needs permissions "contents: write" + # disable issues processing at all (this step is for treat PRs) + days-before-issue-stale: -1 + days-before-issue-close: -1 + ignore-issue-updates: true + remove-issue-stale-when-updated: false + stale-issue-label: " " + + - name: Print outputs for PRs + run: echo ${{ join(steps.stale-prs.outputs.*, ',') }} + + ## Removing private properties from each JSON object and compute array length + ## TODO: Delete these set-* workarounds when resolve actions/stale#806 ? + - name: Set staled + id: set-staled + run: | + echo $INPUT_ISSUES \ + | jq --compact-output --raw-output 'del(.[] | .[to_entries[] | .key | select(startswith("_"))])' \ + | sed -e 's/^/::set-output name=issues::/' + echo $INPUT_ISSUES \ + | jq --raw-output '. | length' \ + | sed -e 's/^/::set-output name=issues-len::/' + + echo $INPUT_PRS \ + | jq --compact-output --raw-output 'del(.[] | .[to_entries[] | .key | select(startswith("_"))])' \ + | sed -e 's/^/::set-output name=prs::/' + echo $INPUT_PRS \ + | jq --raw-output '. | length' \ + | sed -e 's/^/::set-output name=prs-len::/' + env: + INPUT_ISSUES: ${{ steps.stale-issues.outputs.staled-issues-prs }} + INPUT_PRS: ${{ steps.stale-prs.outputs.staled-issues-prs }} + - name: Set closed + id: set-closed + run: | + echo $INPUT_ISSUES \ + | jq --compact-output --raw-output 'del(.[] | .[to_entries[] | .key | select(startswith("_"))])' \ + | sed -e 's/^/::set-output name=issues::/' + echo $INPUT_ISSUES \ + | jq --raw-output '. | length' \ + | sed -e 's/^/::set-output name=issues-len::/' + + echo $INPUT_PRS \ + | jq --compact-output --raw-output 'del(.[] | .[to_entries[] | .key | select(startswith("_"))])' \ + | sed -e 's/^/::set-output name=prs::/' + echo $INPUT_PRS \ + | jq --raw-output '. | length' \ + | sed -e 's/^/::set-output name=prs-len::/' + env: + INPUT_ISSUES: ${{ steps.stale-issues.outputs.closed-issues-prs }} + INPUT_PRS: ${{ steps.stale-prs.outputs.closed-issues-prs }} + + - name: Write job summary + run: | + echo "### Staled issues" \ + >> $GITHUB_STEP_SUMMARY + # render json array to a Markdown table with an optional "No records" message if empty + echo "$STALED_ISSUES" \ + | jq --raw-output 'map("| [#\(.number)](\(env.GITHUB_ISSUES_URL)/\(.number)) | \(.title) |") | join("\n") | if (. == "") then "\nNo records.\n" else "\n| | Title |\n|---:|:------|\n\(.)\n" end' \ + >> $GITHUB_STEP_SUMMARY + + echo "### Staled pull requests" \ + >> $GITHUB_STEP_SUMMARY + # render json array to a Markdown table with an optional "No records" message if empty + echo "$STALED_PRS" \ + | jq --raw-output 'map("| [#\(.number)](\(env.GITHUB_PULL_URL)/\(.number)) | \(.title) |") | join("\n") | if (. == "") then "\nNo records.\n" else "\n| | Title |\n|---:|:------|\n\(.)\n" end' \ + >> $GITHUB_STEP_SUMMARY + + echo "### Closed issues" \ + >> $GITHUB_STEP_SUMMARY + # render json array to a Markdown table with an optional "No records" message if empty + echo "$CLOSED_ISSUES" \ + | jq --raw-output 'map("| [#\(.number)](\(env.GITHUB_ISSUES_URL)/\(.number)) | \(.title) |") | join("\n") | if (. == "") then "\nNo records.\n" else "\n| | Title |\n|---:|:------|\n\(.)\n" end' \ + >> $GITHUB_STEP_SUMMARY + + echo "### Closed pull requests" \ + >> $GITHUB_STEP_SUMMARY + # render json array to a Markdown table with an optional "No records" message if empty + echo "$CLOSED_PRS" \ + | jq --raw-output 'map("| [#\(.number)](\(env.GITHUB_PULL_URL)/\(.number)) | \(.title) |") | join("\n") | if (. == "") then "\nNo records.\n" else "\n| | Title |\n|---:|:------|\n\(.)\n" end' \ + >> $GITHUB_STEP_SUMMARY + env: + GITHUB_ISSUES_URL: ${{ format('{0}/{1}/issues', github.server_url, github.repository) }} + GITHUB_PULL_URL: ${{ format('{0}/{1}/pull', github.server_url, github.repository) }} + STALED_ISSUES: ${{ steps.set-staled.outputs.issues }} + CLOSED_ISSUES: ${{ steps.set-closed.outputs.issues }} + STALED_PRS: ${{ steps.set-staled.outputs.prs }} + CLOSED_PRS: ${{ steps.set-closed.outputs.prs }}