Skip to content

Commit

Permalink
Merge initial design #1
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Bullrich committed Apr 1, 2023
2 parents 3f58325 + 27511bb commit 9e04807
Show file tree
Hide file tree
Showing 12 changed files with 745 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
Dockerfile
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @paritytech/opstooling
108 changes: 108 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Publish package to GitHub Packages
on:
push:
branches:
- main
pull_request:

env:
IMAGE_NAME: action

jobs:
test-image:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
- name: Check that the image builds
run: docker build . --file Dockerfile

test-versions:
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
- name: Extract package.json version
id: package_version
run: echo "VERSION=$(jq '.version' -r package.json)" >> $GITHUB_OUTPUT
- name: Extract action.yml version
uses: mikefarah/yq@master
id: action_image
with:
cmd: yq '.runs.image' 'action.yml'
- name: Parse action.yml version
id: action_version
run: |
echo "IMAGE_VERSION=$(echo $IMAGE_URL | cut -d: -f3)" >> $GITHUB_OUTPUT
env:
IMAGE_URL: ${{ steps.action_image.outputs.result }}
- name: Compare versions
run: |
echo "Verifying that $IMAGE_VERSION from action.yml is the same as $PACKAGE_VERSION from package.json"
[[ $IMAGE_VERSION == $PACKAGE_VERSION ]]
env:
IMAGE_VERSION: ${{ steps.action_version.outputs.IMAGE_VERSION }}
PACKAGE_VERSION: ${{ steps.package_version.outputs.VERSION }}

tag:
if: github.event_name == 'push'
needs: [test-image, test-versions]
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
tagcreated: ${{ steps.autotag.outputs.tagcreated }}
tagname: ${{ steps.autotag.outputs.tagname }}
steps:
- uses: actions/[email protected]
with:
fetch-depth: 0
- uses: butlerlogic/action-autotag@stable
id: autotag
with:
head_branch: master
tag_prefix: "v"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Changelog
uses: Bullrich/[email protected]
id: Changelog
env:
REPO: ${{ github.repository }}
- name: Create Release
if: steps.autotag.outputs.tagname != ''
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.autotag.outputs.tagname }}
release_name: Release ${{ steps.autotag.outputs.tagname }}
body: |
${{ steps.Changelog.outputs.changelog }}
publish:
runs-on: ubuntu-latest
permissions:
packages: write
needs: [tag]
if: needs.tag.outputs.tagname != ''
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build . --file Dockerfile --tag $IMAGE_NAME
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
- name: Push image
run: |
IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ ! -z $TAG ]] && VERSION=$(echo $TAG | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "main" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
env:
TAG: ${{ needs.tag.outputs.tagname }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/node_modules
/dist
yarn-error.log

.DS_Store
.env*
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM node:18 as Builder

WORKDIR /action

COPY package.json yarn.lock ./

RUN yarn install --frozen-lockfile

COPY . .

RUN yarn run build

FROM node:18-slim

COPY --from=Builder /action/dist /action

ENTRYPOINT ["node", "/action/index.js"]
220 changes: 218 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,218 @@
# stale-issues-finder
Finds outdated issues and reports them

# Stale Issue Finder
Finds outdated issues and generates an output data & message.

Intended to be used with a notification action (Slack/Discord/Email/etc look at the example usage).

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.

## Why?

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.

By being agnostic on the result, users can use the output to generate a custom message on their favorite system.

## Example usage

You need to create a file in `.github/workflows` and add the following:

```yml
name: Find stale issues

on:
workflow_dispatch:

jobs:
fetch:
permissions:
issues: read
runs-on: ubuntu-latest
steps:
- name: Fetch issues from here
# We add the id to access to this step outputs
id: stale
uses: paritytech/stale-issues-finder@main
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# optional, how many days since the last action for it to be stale
# defaults to 5
days-stale: 10
# example showing how to use the content
- name: Produce result
run: |
echo "There are $AMOUNT stale issues in this repository"
echo "$ACTION_ISSUES"
env:
# a number with the amount of stale issues in the repository
AMOUNT: ${{ steps.stale.outputs.stale }}"
# a formatted markdown message
ACTION_ISSUES: ${{ steps.stale.outputs.message }}"
```
### Inputs
You can find all the inputs in [the action file](./action.yml) but let's walk through each one of them:
- `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.
- **required**
- If using on the same repo, you can simply use `${{ github.token }}`.
- `repo`: name of the repository. Example: `https://github.com/paritytech/REPO-NAME-GOES-HERE`
- **defaults** to the repo where this action will be run.
- Setting this value and `owner` allows you to run this action in other repositories (useful if you want to aggregate all the stale issues)
- If set, be sure to read the [`accessing other repositories`](#accessing-other-repositories) section.
- `owner`: name of the organization/user where the repository is. Example: `https://github.com/OWNER-NAME/stale-issues-finder`
- **defaults** to the organization where this action is ran.
- `days-stale`: Amount of days since the last activity for an issue to be considered *stale*.
- **default**: 5

#### Accessing other repositories

The action has the ability to access other repositories but if it can read it or not depends of the repository's visibility.

The default `${{ github.token }}` variable has enough permissions to read the issues in **public repositories**.
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.

### Outputs
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.
You can find all the outputs in [the action file](./action.yml) but let's walk through each one of them:
- `stale`: Amount of stale issues found in the step. It's only the number (`0`, `4`, etc)
- `repo`: Organization and repo name. Written in the format of `owner/repo`.
- `message`: A markdown message with a list of all the stale issues. See the example below.
- If no stale issues were found, it will be `## Repo owner/repo has no stale issues` instead.
- `data`: A json object with the data of the stale issues. See the example below for the format of the data.

**The `message` and `data` objects are sorted from oldest last change to newest.**

#### Markdown message

An example of how the markdown would be produced for this repository:
### Repo paritytech/action-project-sync has 3 stale issues
- [Stop AI from controlling the world](https://github.com/paritytech/stale-issues-finder/issues/15) - Stale for 25 days
- [Lint the repo](https://github.com/paritytech/stale-issues-finder/issues/12) - Stale for 21 days
- [Help me with reading](https://github.com/paritytech/stale-issues-finder/issues/3) - Stale for 18 days

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.

#### JSON Data
```json
[
{
"url": "https://github.com/paritytech/stale-issues-finder/issues/15",
"title": "Stop AI from controlling the world",
"daysStale": "25"
},
{
"url": "https://github.com/paritytech/stale-issues-finder/issues/12",
"title": "Lint the repo",
"daysStale": "21"
},
{
"url": "https://github.com/paritytech/stale-issues-finder/issues/3",
"title": "Help me with reading",
"daysStale": "18"
}
]
```

### Using a GitHub app instead of a PAT
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:
- Repository permissions:
- Issues
- [x] Read

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:
- After you create the app, copy the *App ID* and the *private key* and set them as secrets.
- Then you need to modify the workflow file to have an extra step:
```yml
steps:
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
- name: Fetch issues from here
id: stale
uses: paritytech/stale-issues-finder@main
with:
days-stale: 10
# The previous step generates a token which is used as the input for this action
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
```

Be aware that this is needed only to read issues from **external private repositories**.
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.

## Example workflow

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.

This issue needs to run on 3 different repositories:
- The current repository
- `example/abc` repository
- `example/xyz` repository

```yml
name: Find stale issues
on:
workflow_dispatch:
schedule:
- cron: '0 9 * * 1'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Fetch issues from here
id: local
uses: paritytech/stale-issues-finder@main
with:
GITHUB_TOKEN: ${{ github.token }}
- name: Fetch abc issues
id: abc
uses: paritytech/stale-issues-finder@main
with:
GITHUB_TOKEN: ${{ github.token }}
owner: example
repo: abc
- name: Fetch xyz issues
id: xyz
uses: paritytech/stale-issues-finder@main
with:
GITHUB_TOKEN: ${{ github.token }}
owner: example
repo: xyz
- name: Post to a Slack channel
id: slack
uses: slackapi/[email protected]
with:
channel-id: 'CHANNEL_ID,ANOTHER_CHANNEL_ID'
slack-message: "Stale issues this week: \n$LOCAL_ISSUES \n$ABC_ISSUES \n$XYZ_ISSUES"
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
LOCAL_ISSUES: ${{ steps.local.outputs.message }}"
ABC_ISSUES: ${{ steps.abc.outputs.message }}"
XYZ_ISSUES: ${{ steps.xyz.outputs.message }}"
```

This will produce a message similar to the following:

Stale issues this week:
### Repo example/local has 1 stale issues
- [Stop AI from controlling the world](https://github.com/example/local/issues/15) - Stale for 25 days
### Repo example/abc has 2 stale issues
- [Lint the repo](https://github.com/example/abc/issues/12) - Stale for 21 days
- [Help me with reading](https://github.com/example/abc/issues/3) - Stale for 18 days
### Repo example/xyz has 3 stale issues
- [La la la](https://github.com/example/xyz/issues/15) - Stale for 25 days
- [Help with lalilulelo](https://github.comexample/xyz/issues/12) - Stale for 21 days
- [Fix the issue with the word 'Patriot'](https://github.com/example/xyz/issues/3) - Stale for 18 days

## Development
To work on this app, you require
- `Node 18.x`
- `yarn`

Use `yarn install` to set up the project.

`yarn build` compiles the TypeScript code to JavaScript.
33 changes: 33 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: "Stale Issues Finder"
description: "Find what issues have been stale for a given time"
author: paritytech
branding:
icon: zoom-in
color: white
inputs:
GITHUB_TOKEN:
required: true
description: The token to access the repo
repo:
required: false
description: The repository to fetch the issues from
owner:
required: false
description: The name of the org/user that owns the repository
days-stale:
required: false
description: How many days have to pass to consider an action "stale"
default: '5'
outputs:
repo:
description: 'The name of the repo in owner/repo pattern'
data:
description: 'A JSON object with the data'
message:
description: 'A markdown formatted message'
stale:
description: 'Amount of stale issues. 0 if none found.'

runs:
using: 'docker'
image: 'docker://ghcr.io/paritytech/stale-issues-finder/action:0.0.1'
Loading

0 comments on commit 9e04807

Please sign in to comment.