Skip to content

Commit 9e04807

Browse files
authored
Merge initial design #1
This commit merges the first version of the action. It works with the minimum necessary fields but it has enough to develop more features on top.
2 parents 3f58325 + 27511bb commit 9e04807

12 files changed

Lines changed: 745 additions & 2 deletions

File tree

.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
dist
3+
Dockerfile

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @paritytech/opstooling

.github/workflows/publish.yml

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
name: Publish package to GitHub Packages
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
8+
env:
9+
IMAGE_NAME: action
10+
11+
jobs:
12+
test-image:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v3.3.0
16+
- name: Check that the image builds
17+
run: docker build . --file Dockerfile
18+
19+
test-versions:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v3.3.0
23+
- name: Extract package.json version
24+
id: package_version
25+
run: echo "VERSION=$(jq '.version' -r package.json)" >> $GITHUB_OUTPUT
26+
- name: Extract action.yml version
27+
uses: mikefarah/yq@master
28+
id: action_image
29+
with:
30+
cmd: yq '.runs.image' 'action.yml'
31+
- name: Parse action.yml version
32+
id: action_version
33+
run: |
34+
echo "IMAGE_VERSION=$(echo $IMAGE_URL | cut -d: -f3)" >> $GITHUB_OUTPUT
35+
env:
36+
IMAGE_URL: ${{ steps.action_image.outputs.result }}
37+
- name: Compare versions
38+
run: |
39+
echo "Verifying that $IMAGE_VERSION from action.yml is the same as $PACKAGE_VERSION from package.json"
40+
[[ $IMAGE_VERSION == $PACKAGE_VERSION ]]
41+
env:
42+
IMAGE_VERSION: ${{ steps.action_version.outputs.IMAGE_VERSION }}
43+
PACKAGE_VERSION: ${{ steps.package_version.outputs.VERSION }}
44+
45+
tag:
46+
if: github.event_name == 'push'
47+
needs: [test-image, test-versions]
48+
runs-on: ubuntu-latest
49+
permissions:
50+
contents: write
51+
outputs:
52+
tagcreated: ${{ steps.autotag.outputs.tagcreated }}
53+
tagname: ${{ steps.autotag.outputs.tagname }}
54+
steps:
55+
- uses: actions/checkout@v3.3.0
56+
with:
57+
fetch-depth: 0
58+
- uses: butlerlogic/action-autotag@stable
59+
id: autotag
60+
with:
61+
head_branch: master
62+
tag_prefix: "v"
63+
env:
64+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
65+
- name: Changelog
66+
uses: Bullrich/generate-release-changelog@2.0.2
67+
id: Changelog
68+
env:
69+
REPO: ${{ github.repository }}
70+
- name: Create Release
71+
if: steps.autotag.outputs.tagname != ''
72+
uses: actions/create-release@latest
73+
env:
74+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75+
with:
76+
tag_name: ${{ steps.autotag.outputs.tagname }}
77+
release_name: Release ${{ steps.autotag.outputs.tagname }}
78+
body: |
79+
${{ steps.Changelog.outputs.changelog }}
80+
publish:
81+
runs-on: ubuntu-latest
82+
permissions:
83+
packages: write
84+
needs: [tag]
85+
if: needs.tag.outputs.tagname != ''
86+
steps:
87+
- uses: actions/checkout@v3
88+
- name: Build image
89+
run: docker build . --file Dockerfile --tag $IMAGE_NAME
90+
- name: Log into registry
91+
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
92+
- name: Push image
93+
run: |
94+
IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
95+
# Change all uppercase to lowercase
96+
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
97+
# Strip git ref prefix from version
98+
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
99+
# Strip "v" prefix from tag name
100+
[[ ! -z $TAG ]] && VERSION=$(echo $TAG | sed -e 's/^v//')
101+
# Use Docker `latest` tag convention
102+
[ "$VERSION" == "main" ] && VERSION=latest
103+
echo IMAGE_ID=$IMAGE_ID
104+
echo VERSION=$VERSION
105+
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
106+
docker push $IMAGE_ID:$VERSION
107+
env:
108+
TAG: ${{ needs.tag.outputs.tagname }}

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/node_modules
2+
/dist
3+
yarn-error.log
4+
5+
.DS_Store
6+
.env*

Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FROM node:18 as Builder
2+
3+
WORKDIR /action
4+
5+
COPY package.json yarn.lock ./
6+
7+
RUN yarn install --frozen-lockfile
8+
9+
COPY . .
10+
11+
RUN yarn run build
12+
13+
FROM node:18-slim
14+
15+
COPY --from=Builder /action/dist /action
16+
17+
ENTRYPOINT ["node", "/action/index.js"]

README.md

Lines changed: 218 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,218 @@
1-
# stale-issues-finder
2-
Finds outdated issues and reports them
1+
2+
# Stale Issue Finder
3+
Finds outdated issues and generates an output data & message.
4+
5+
Intended to be used with a notification action (Slack/Discord/Email/etc look at the example usage).
6+
7+
Works great with the [`workflow_dispatch`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch) or [`schedule`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule) action events.
8+
9+
## Why?
10+
11+
This action is intended for the case where a repository (or an organization) needs to find out what issues have been stale for a while.
12+
13+
By being agnostic on the result, users can use the output to generate a custom message on their favorite system.
14+
15+
## Example usage
16+
17+
You need to create a file in `.github/workflows` and add the following:
18+
19+
```yml
20+
name: Find stale issues
21+
22+
on:
23+
workflow_dispatch:
24+
25+
jobs:
26+
fetch:
27+
permissions:
28+
issues: read
29+
runs-on: ubuntu-latest
30+
steps:
31+
- name: Fetch issues from here
32+
# We add the id to access to this step outputs
33+
id: stale
34+
uses: paritytech/stale-issues-finder@main
35+
with:
36+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37+
# optional, how many days since the last action for it to be stale
38+
# defaults to 5
39+
days-stale: 10
40+
# example showing how to use the content
41+
- name: Produce result
42+
run: |
43+
echo "There are $AMOUNT stale issues in this repository"
44+
echo "$ACTION_ISSUES"
45+
env:
46+
# a number with the amount of stale issues in the repository
47+
AMOUNT: ${{ steps.stale.outputs.stale }}"
48+
# a formatted markdown message
49+
ACTION_ISSUES: ${{ steps.stale.outputs.message }}"
50+
```
51+
52+
### Inputs
53+
You can find all the inputs in [the action file](./action.yml) but let's walk through each one of them:
54+
55+
- `GITHUB_TOKEN`: Token to access to the repository issues. If you are access a different repository be sure to read the [`accessing other repositories`](#accessing-other-repositories) section.
56+
- **required**
57+
- If using on the same repo, you can simply use `${{ github.token }}`.
58+
- `repo`: name of the repository. Example: `https://github.com/paritytech/REPO-NAME-GOES-HERE`
59+
- **defaults** to the repo where this action will be run.
60+
- Setting this value and `owner` allows you to run this action in other repositories (useful if you want to aggregate all the stale issues)
61+
- If set, be sure to read the [`accessing other repositories`](#accessing-other-repositories) section.
62+
- `owner`: name of the organization/user where the repository is. Example: `https://github.com/OWNER-NAME/stale-issues-finder`
63+
- **defaults** to the organization where this action is ran.
64+
- `days-stale`: Amount of days since the last activity for an issue to be considered *stale*.
65+
- **default**: 5
66+
67+
#### Accessing other repositories
68+
69+
The action has the ability to access other repositories but if it can read it or not depends of the repository's visibility.
70+
71+
The default `${{ github.token }}` variable has enough permissions to read the issues in **public repositories**.
72+
If you want this action to access to the issues in a private repository, then you will need a `Personal Access Token` with `repo` permissions.
73+
74+
### Outputs
75+
Outputs are needed for your chained actions. If you want to use this information, remember to set an `id` field in the step so you can access it.
76+
You can find all the outputs in [the action file](./action.yml) but let's walk through each one of them:
77+
- `stale`: Amount of stale issues found in the step. It's only the number (`0`, `4`, etc)
78+
- `repo`: Organization and repo name. Written in the format of `owner/repo`.
79+
- `message`: A markdown message with a list of all the stale issues. See the example below.
80+
- If no stale issues were found, it will be `## Repo owner/repo has no stale issues` instead.
81+
- `data`: A json object with the data of the stale issues. See the example below for the format of the data.
82+
83+
**The `message` and `data` objects are sorted from oldest last change to newest.**
84+
85+
#### Markdown message
86+
87+
An example of how the markdown would be produced for this repository:
88+
### Repo paritytech/action-project-sync has 3 stale issues
89+
- [Stop AI from controlling the world](https://github.com/paritytech/stale-issues-finder/issues/15) - Stale for 25 days
90+
- [Lint the repo](https://github.com/paritytech/stale-issues-finder/issues/12) - Stale for 21 days
91+
- [Help me with reading](https://github.com/paritytech/stale-issues-finder/issues/3) - Stale for 18 days
92+
93+
You can send the data in this format to a Slack/Discord/Matrix server. You can also create a new GitHub issue with this format.
94+
95+
#### JSON Data
96+
```json
97+
[
98+
{
99+
"url": "https://github.com/paritytech/stale-issues-finder/issues/15",
100+
"title": "Stop AI from controlling the world",
101+
"daysStale": "25"
102+
},
103+
{
104+
"url": "https://github.com/paritytech/stale-issues-finder/issues/12",
105+
"title": "Lint the repo",
106+
"daysStale": "21"
107+
},
108+
{
109+
"url": "https://github.com/paritytech/stale-issues-finder/issues/3",
110+
"title": "Help me with reading",
111+
"daysStale": "18"
112+
}
113+
]
114+
```
115+
116+
### Using a GitHub app instead of a PAT
117+
In some cases, specially in big organizations, it is more organized to use a GitHub app to authenticate, as it allows us to give it permissions per repository and we can fine-grain them even better. If you wish to do that, you need to create a GitHub app with the following permissions:
118+
- Repository permissions:
119+
- Issues
120+
- [x] Read
121+
122+
Because this project is intended to be used with a token we need to do an extra step to generate one from the GitHub app:
123+
- After you create the app, copy the *App ID* and the *private key* and set them as secrets.
124+
- Then you need to modify the workflow file to have an extra step:
125+
```yml
126+
steps:
127+
- name: Generate token
128+
id: generate_token
129+
uses: tibdex/github-app-token@v1
130+
with:
131+
app_id: ${{ secrets.APP_ID }}
132+
private_key: ${{ secrets.PRIVATE_KEY }}
133+
- name: Fetch issues from here
134+
id: stale
135+
uses: paritytech/stale-issues-finder@main
136+
with:
137+
days-stale: 10
138+
# The previous step generates a token which is used as the input for this action
139+
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
140+
```
141+
142+
Be aware that this is needed only to read issues from **external private repositories**.
143+
If the issue is in the same repository, or the target repository is public, the default `${{ github.token }}` has enough access to read the issues.
144+
145+
## Example workflow
146+
147+
Let's make an example. We want to have a workflow that runs every Monday at 9 in the morning and it informs through a slack message in a channel. We can also trigger it manually if we want to.
148+
149+
This issue needs to run on 3 different repositories:
150+
- The current repository
151+
- `example/abc` repository
152+
- `example/xyz` repository
153+
154+
```yml
155+
name: Find stale issues
156+
157+
on:
158+
workflow_dispatch:
159+
schedule:
160+
- cron: '0 9 * * 1'
161+
162+
jobs:
163+
sync:
164+
runs-on: ubuntu-latest
165+
steps:
166+
- name: Fetch issues from here
167+
id: local
168+
uses: paritytech/stale-issues-finder@main
169+
with:
170+
GITHUB_TOKEN: ${{ github.token }}
171+
- name: Fetch abc issues
172+
id: abc
173+
uses: paritytech/stale-issues-finder@main
174+
with:
175+
GITHUB_TOKEN: ${{ github.token }}
176+
owner: example
177+
repo: abc
178+
- name: Fetch xyz issues
179+
id: xyz
180+
uses: paritytech/stale-issues-finder@main
181+
with:
182+
GITHUB_TOKEN: ${{ github.token }}
183+
owner: example
184+
repo: xyz
185+
- name: Post to a Slack channel
186+
id: slack
187+
uses: slackapi/slack-github-action@v1.23.0
188+
with:
189+
channel-id: 'CHANNEL_ID,ANOTHER_CHANNEL_ID'
190+
slack-message: "Stale issues this week: \n$LOCAL_ISSUES \n$ABC_ISSUES \n$XYZ_ISSUES"
191+
env:
192+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
193+
LOCAL_ISSUES: ${{ steps.local.outputs.message }}"
194+
ABC_ISSUES: ${{ steps.abc.outputs.message }}"
195+
XYZ_ISSUES: ${{ steps.xyz.outputs.message }}"
196+
```
197+
198+
This will produce a message similar to the following:
199+
200+
Stale issues this week:
201+
### Repo example/local has 1 stale issues
202+
- [Stop AI from controlling the world](https://github.com/example/local/issues/15) - Stale for 25 days
203+
### Repo example/abc has 2 stale issues
204+
- [Lint the repo](https://github.com/example/abc/issues/12) - Stale for 21 days
205+
- [Help me with reading](https://github.com/example/abc/issues/3) - Stale for 18 days
206+
### Repo example/xyz has 3 stale issues
207+
- [La la la](https://github.com/example/xyz/issues/15) - Stale for 25 days
208+
- [Help with lalilulelo](https://github.comexample/xyz/issues/12) - Stale for 21 days
209+
- [Fix the issue with the word 'Patriot'](https://github.com/example/xyz/issues/3) - Stale for 18 days
210+
211+
## Development
212+
To work on this app, you require
213+
- `Node 18.x`
214+
- `yarn`
215+
216+
Use `yarn install` to set up the project.
217+
218+
`yarn build` compiles the TypeScript code to JavaScript.

action.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: "Stale Issues Finder"
2+
description: "Find what issues have been stale for a given time"
3+
author: paritytech
4+
branding:
5+
icon: zoom-in
6+
color: white
7+
inputs:
8+
GITHUB_TOKEN:
9+
required: true
10+
description: The token to access the repo
11+
repo:
12+
required: false
13+
description: The repository to fetch the issues from
14+
owner:
15+
required: false
16+
description: The name of the org/user that owns the repository
17+
days-stale:
18+
required: false
19+
description: How many days have to pass to consider an action "stale"
20+
default: '5'
21+
outputs:
22+
repo:
23+
description: 'The name of the repo in owner/repo pattern'
24+
data:
25+
description: 'A JSON object with the data'
26+
message:
27+
description: 'A markdown formatted message'
28+
stale:
29+
description: 'Amount of stale issues. 0 if none found.'
30+
31+
runs:
32+
using: 'docker'
33+
image: 'docker://ghcr.io/paritytech/stale-issues-finder/action:0.0.1'

0 commit comments

Comments
 (0)