Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/json config #80

Merged
merged 12 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/auto-label.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
labelsSynonyms: {
bug: ['error', 'need fix', 'not working'],
enhancement: ['upgrade'],
question: ['help', 'how can i']
},
labelsNotAllowed: [
'documentation',
'duplicate',
'good first issue',
'help wanted',
'invalid'
],
defaultLabels: ['triage']
}
20 changes: 20 additions & 0 deletions .github/workflows/auto-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Labeling new issue

on:
issues:
types: ['opened']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
.github/workflows/
# .github/workflows/auto-label.json5
# .github/workflows/auto-label.jsonc
# .github/workflows/auto-label.json
sparse-checkout-cone-mode: false
- uses: Renato66/auto-label@main
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src/__mock__/config/invalid/invalid3.json
readme.md
93 changes: 72 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,14 @@

![Unit test](https://github.com/Renato66/auto-label/workflows/Unit%20test/badge.svg)
[![Auto Label](https://github.com/Renato66/auto-label/workflows/Labeling%20new%20issue/badge.svg)](https://github.com/Renato66/auto-label)
[![codecov](https://codecov.io/gh/Renato66/auto-label/branch/master/graph/badge.svg)](https://codecov.io/gh/Renato66/auto-label)

![image](https://user-images.githubusercontent.com/9284273/79672530-57c1db80-81a9-11ea-900c-3b4f73984e0a.png)

The Auto label action will check for every new issue and automatically adds a label based on the body of the issue. This means that finding specific issues will be much easier.

> [!WARNING]
> The main branch is being refactored, use the stable one at [master](https://github.com/Renato66/auto-label/tree/master) subscribe to [v3](https://github.com/Renato66/auto-label/issues/75) to get the latest version when it's released

## Creating

Check out the app to make a YAML file [here](https://renato66.github.io/auto-label/).

or

add a file to `.github/workflows/issue.yml`
Add a file to `.github/workflows/auto-label.yml`

```yml
name: Labeling new issue
Expand All @@ -28,24 +20,48 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: Renato66/auto-label@v2
- uses: actions/checkout@v4
with:
sparse-checkout: |
.github/workflows
sparse-checkout-cone-mode: false
- uses: Renato66/auto-label@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
ignore-comments: true
labels-synonyms: '{"bug":["error","need fix","not working"],"enhancement":["upgrade"],"question":["help"]}'
labels-not-allowed: '["good first issue"]'
default-labels: '["help wanted"]'
```

Add a config file to `.github/workflows/auto-label.json5`

```json5
// see inputs for more examples
{
labelsSynonyms: {
bug: ['error', 'need fix', 'not working'],
enhancement: ['upgrade'],
question: ['help', 'how can i']
},
labelsNotAllowed: [
'documentation',
'duplicate',
'good first issue',
'help wanted',
'invalid'
],
defaultLabels: ['triage'],
ignoreComments: true
}
```

## Inputs

| Name | Description | Required | Default | Examples |
| ------------------ | ----------------------------------- | -------- | ------- | :------------------------: |
| repo-token | GitHub token for the repository | true | - | [...](#repo-token) |
| ignore-comments | Ignore labels inside issue comments | false | true | [...](#ignore-comments) |
| labels-synonyms | Text synonyms for labels | false | - | [...](#labels-synonyms) |
| labels-not-allowed | Labels to ignore | false | - | [...](#labels-not-allowed) |
| default-labels | Labels that will always be set | false | - | [...](#default-labels) |
| Name | Description | Required | Default | Examples |
| ------------------ | ----------------------------------- | -------- | ---------------------------------- | :------------------------: |
| repo-token | GitHub token for the repository | true | - | [...](#repo-token) |
| configuration-file | Configuration file path | true | .github/workflows/auto-label.json5 | [...](#configuration-file) |
| ignore-comments | Ignore labels inside issue comments | false | true | [...](#ignore-comments) |
| labels-synonyms | Text synonyms for labels | false | - | [...](#labels-synonyms) |
| labels-not-allowed | Labels to ignore | false | - | [...](#labels-not-allowed) |
| default-labels | Labels that will always be set | false | - | [...](#default-labels) |

### Repo Token

Expand All @@ -55,6 +71,41 @@ Repo token is provided automatically by GitHub; just need to add
repo-token: ${{ secrets.GITHUB_TOKEN }}
```

### Configuration File

Configuration file can be created at any place at your repository, it will need another action to get the file like:

```yml
- uses: actions/checkout@v4
with:
sparse-checkout: |
.github/workflows/
sparse-checkout-cone-mode: false
```

and it will look for any file named auto-label with the extension `JSON` or `JSON5` or `JSONC` but you can also define a specific extension

```yml
- uses: actions/checkout@v4
with:
sparse-checkout: |
.github/workflows/auto-label.json5
sparse-checkout-cone-mode: false
```

to set another place to store your configuration file, you should checkout and point with `configuration-file` input:
Renato66 marked this conversation as resolved.
Show resolved Hide resolved

```yml
- uses: actions/checkout@v4
with:
sparse-checkout: |
src/actions/configuration.json
sparse-checkout-cone-mode: false
- uses: Renato66/auto-label@v3
with:
configuration-file: 'src/actions/configuration.json'
```

#### Change bot appearance

If you want to change who added the labels, you can provide a user token
Expand Down
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"dependencies": {
"@actions/core": "1.10.1",
"@actions/github": "6.0.0"
"@actions/github": "6.0.0",
"json5": "^2.2.3"
}
}
8 changes: 8 additions & 0 deletions src/__mock__/config/auto-label.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"labelsSynonyms": {
"bug": ["error"]
},
"labelsNotAllowed": ["documentation"],
"defaultLabels": ["triage"],
"ignoreComments": true
}
13 changes: 13 additions & 0 deletions src/__mock__/config/auto-label.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
// comment test
labelsSynonyms: {
bug: [
'error'
// trailing comma
]
},
// single quote
labelsNotAllowed: ['documentation'],
defaultLabels: ['triage'],
ignoreComments: true
}
9 changes: 9 additions & 0 deletions src/__mock__/config/auto-label.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
// comment test
"labelsSynonyms": {
"bug": ["error"]
},
"labelsNotAllowed": ["documentation"],
"defaultLabels": ["triage"],
"ignoreComments": true
}
1 change: 1 addition & 0 deletions src/__mock__/config/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
6 changes: 6 additions & 0 deletions src/__mock__/config/invalid/invalid1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"labels-synonyms": "expected: Record<string, string[]>",
"labels-not-allowed": "expected: string[]",
"default-labels": "expected: string[]",
"ignoreComments": "expected: boolean"
}
1 change: 1 addition & 0 deletions src/__mock__/config/invalid/invalid2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"invalid json"
1 change: 1 addition & 0 deletions src/__mock__/config/invalid/invalid3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
not-valid-json
8 changes: 8 additions & 0 deletions src/__mock__/config/valid/valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"labelsSynonyms": {
"bug": ["error"]
},
"labelsNotAllowed": ["documentation"],
"defaultLabels": ["triage"],
"ignoreComments": true
}
28 changes: 24 additions & 4 deletions src/domain/getConfigFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@ describe('getConfigFile', () => {
})
test('returns empty array when labels-not-allowed input is empty', () => {
mock.module('@actions/core', () => ({
getInput: jest.fn(() => undefined),
getBooleanInput: jest.fn(() => undefined)
getInput: jest.fn((input: string) => {
const options: Record<string, string> = {
'repo-token': 'mockedToken',
'configuration-file': 'src/__mock__/config/empty.json',
'labels-not-allowed': ''
}
return options[input] || undefined
})
}))
const result1 = getConfigFile()
expect(result1.labelsNotAllowed).toEqual([])
mock.module('@actions/core', () => ({
getInput: jest.fn(() => '')
getInput: jest.fn((input: string) => {
const options: Record<string, any> = {
'repo-token': 'mockedToken',
'configuration-file': 'src/__mock__/config/empty.json',
'labels-not-allowed': undefined
}
return options[input] || undefined
})
}))
const result2 = getConfigFile()
expect(result2.labelsNotAllowed).toEqual([])
Expand All @@ -23,7 +36,14 @@ describe('getConfigFile', () => {
test('returns parsed array from labels-not-allowed input', () => {
const labels = ['label1', 'label2']
mock.module('@actions/core', () => ({
getInput: jest.fn(() => JSON.stringify(labels))
getInput: jest.fn((input: string) => {
const options: Record<string, string> = {
'repo-token': 'mockedToken',
'configuration-file': 'src/__mock__/config/empty.json',
'labels-not-allowed': JSON.stringify(labels)
}
return options[input] || undefined
})
}))
const result = getConfigFile()
expect(result.labelsNotAllowed).toEqual(labels)
Expand Down
18 changes: 16 additions & 2 deletions src/domain/getConfigFile.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { getInput } from './getInput'
import { getLabelConfigs } from './getLabelConfigs'

export const getConfigFile = () => {
export type Config = {
labelsNotAllowed: string[]
defaultLabels: string[]
labelsSynonyms: Record<string, string[]>
ignoreComments: boolean
}

export const getConfigFile = (): Config => {
const configPath = getInput<string>(
'configuration-file',
'.github/workflows/'
)
const labelsNotAllowed = getInput<string[]>('labels-not-allowed', [])
const defaultLabels = getInput<string[]>('default-labels', [])
const labelsSynonyms = getInput<Record<string, string[]>>(
'labels-synonyms',
{}
)
const ignoreComments = getInput('ignore-comments', true)
const config = getLabelConfigs(configPath)

return {
labelsNotAllowed,
defaultLabels,
labelsSynonyms,
ignoreComments
ignoreComments,
...config
}
}
2 changes: 1 addition & 1 deletion src/domain/getInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ export const getInput = <T>(field: string, fallback: T): T => {
case 'boolean':
return getBooleanInput(field, fallback) as T
default:
return core.getInput(field) as T
return (core.getInput(field) as T) || fallback
Renato66 marked this conversation as resolved.
Show resolved Hide resolved
}
}
52 changes: 52 additions & 0 deletions src/domain/getLabelConfigs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect, describe, test, mock, jest } from 'bun:test'
import { getLabelConfigs } from './getLabelConfigs'
import * as core from '@actions/core'

const configurationPath = 'src/__mock__/config'
const defaultConfig = {
labelsSynonyms: {
bug: ['error']
},
labelsNotAllowed: ['documentation'],
defaultLabels: ['triage'],
ignoreComments: true
}
describe('getLabelConfigs', () => {
test('should return label configurations from a valid JSON folder path', () => {
const options = [`${configurationPath}/`, `${configurationPath}`]
options.forEach((elem) => {
const result = getLabelConfigs(elem)
expect(result).toEqual(defaultConfig)
})
})

test('should return label configurations from a valid JSONC file path', () => {
const result = getLabelConfigs(`${configurationPath}/auto-label.jsonc`)
expect(result).toEqual(defaultConfig)
})

test('should return label configurations from a valid JSON5 file path', () => {
const result = getLabelConfigs(`${configurationPath}/auto-label.json5`)
expect(result).toEqual(defaultConfig)
})

test('should return an empty object if the configuration file is not valid', () => {
const options = [
`${configurationPath}/invalid/invalid1.json`,
`${configurationPath}/invalid/invalid2.json`
]
options.forEach((elem) => {
const result = getLabelConfigs(elem)
expect(result).toEqual({})
})
})

test('should send an warning if file is not readable', () => {
mock.module('@actions/core', () => ({
warning: jest.fn()
}))
const result = getLabelConfigs(`${configurationPath}/invalid/invalid3.json`)
expect(result).toEqual({})
mock.module('@actions/core', () => core)
})
})
Loading
Loading