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

issue 1 in pr #16

Open
wants to merge 6 commits into
base: bubkoo-patch-1
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion .github/workflows/auto-assign.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ jobs:
- name: 📦 Build
run: |
yarn build
- name: 🔑 Generate Token
uses: wow-actions/use-app-token@v2
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
- uses: ./
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ env.BOT_TOKEN }}
addReviewers: true
addAssignees: true
numberOfReviewers: 1
Expand Down
51 changes: 35 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
<h1 align="center">Auto Assign</h1>
<p align="center"><strong>Automatically add reviewers/assignees to issues/PRs</strong></p>
<p align="center">Automatically add reviewers/assignees to issues/PRs</p>

## Features
- [Randomly](https://lodash.com/docs/#sampleSize) pick assignees and reviewers from candidate list.
- Automatically ignore invalid Github username.
- Automatically skip assigned issues/PRs and reviewer requested PRs.
- **Try-to** pick the member of team as assignee when adding [team](https://docs.github.com/en/organizations/organizing-members-into-teams/about-teams) to assignees.


**Note that** the default `${{ secrets.GITHUB_TOKEN }}` does not have the permission to **add teams as reviewers** or to **list members of a team**. As a workaround:

- First, [create a personal access token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with `repo` and `admin:org` permissions.
- Then, make the PAT available to our actions by [adding the token as a secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets).
- Finally, replace the `GITHUB_TOKEN` with the new secret, e.g. `GITHUB_TOKEN: ${{ secrets.NAME_OF_MY_SECRET_CONTAINING_PAT_WITH_REPO_ACCESS }}` instead of `GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}`.

Or with a cool but slightly cumbersome solution: create a private [github app](https://probot.github.io/) for your org with custom permissions and avatar, then [use the app token in out workflow](https://github.com/wow-actions/use-app-token), e.g. [wow-actions-bot](https://github.com/apps/wow-actions-bot).






## Usage

Expand All @@ -16,7 +36,7 @@ jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: wow-actions/auto-assign@v2
- uses: wow-actions/auto-assign@v3
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# using the `org/team_slug` or `/team_slug` syntax to add git team as reviewers
Expand All @@ -37,20 +57,19 @@ Various inputs are defined to let you configure the action:

> Note: [Workflow command and parameter names are not case-sensitive](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#about-workflow-commands).

| Name | Description | Default |
|---------------------|-----------------------------------------------------------------------------------------------------------|---------|
| `GITHUB_TOKEN` | The GitHub token for authentication | N/A |
| `addReviewers` | Set to `true` to add reviewers to PRs. | `true` |
| `addAssignees` | Set to `true` to add assignees to issues/PRs. Set to `'author'` to add issue/PR's author as a assignee. | `true` |
| `reviewers` | A list of reviewers(GitHub user name) to be added to PR. | `[]` |
| `assignees` | A list of assignees(GitHub user name) to be added to issue/PR. Uses `reviewers` if not set. file | `[]` |
| `numberOfReviewers` | Number of reviewers added to the PR. Set `0` to add all the reviewers. | `0` |
| `numberOfAssignees` | Number of assignees added to the PR. Set `0` to add all the assignees. Uses `numberReviewers` if not set. | `0` |
| `skipDraft` | Set to `false` to run on draft PRs. | `true` |
| `skipKeywords` | A list of keywords to be skipped the process if issue/PR's title include it. | `[]` |
| `includeLabels` | Only to run when issue/PR has one of the label. | `[]` |
| `excludeLabels` | Not to run when issue/PR has one of the label. | `[]` |

| Name | Description | Default |
|---------------------|-----------------------------------------------------------------------------------------------------------------|---------|
| `GITHUB_TOKEN` | The GitHub token for authentication | N/A |
| `addReviewers` | Set to `true` to add reviewers to PRs. | `true` |
| `addAssignees` | Set to `true` to add assignees to issues/PRs. | `true` |
| `reviewers` | Candidate list of reviewers(GitHub username) to be added to PR. | `[]` |
| `assignees` | Candidate list of assignees(GitHub user name) to be added to issue/PR. Uses `reviewers` if not set. | `[]` |
| `numberOfReviewers` | Number of reviewers added to the PR. Set `0` to add all the reviewers. | `0` |
| `numberOfAssignees` | Number of assignees added to the issue/PR. Set `0` to add all the assignees. Uses `numberReviewers` if not set. | `0` |
| `skipDraft` | Set to `false` to run on draft PRs. | `true` |
| `skipKeywords` | A list of keywords to be skipped the process if issue/PR's title include it. | `[]` |
| `includeLabels` | Only to run when issue/PR has one of the label. | `[]` |
| `excludeLabels` | Not to run when issue/PR has one of the label. | `[]` |

## License

Expand Down
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ inputs:
required: false
default: true
addAssignees:
description: Set to true to add assignees to PRs. Set to 'author' to add PR's author as a assignee.
description: Set to true to add assignees to PRs.
required: false
default: author
default: true
reviewers:
description: A list of reviewers(GitHub user name) to be added to PRs.
required: false
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "auto-assign",
"description": "Automatically add reviewers/assignees to issues/PRs",
"version": "2.1.0",
"version": "3.0.0",
"main": "dist/index.js",
"repository": "https://github.com/wow-actions/auto-assign",
"files": [
Expand Down
111 changes: 36 additions & 75 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { getInputs } from './inputs'
async function run() {
try {
const { context } = github
const payload = context.payload.pull_request || context.payload.issue

core.debug(`event: ${context.eventName}`)
core.debug(`action: ${context.payload.action}`)

const pr = context.payload.pull_request
const issue = context.payload.issue
const payload = pr || issue
const actions = ['opened', 'edited', 'labeled', 'unlabeled']
if (
payload &&
Expand All @@ -21,105 +23,64 @@ async function run() {
const inputs = getInputs()
core.debug(`inputs: \n${JSON.stringify(inputs, null, 2)}`)

if (context.payload.pull_request) {
if (payload.draft && inputs.skipDraft !== false) {
return util.skip('is draft')
}
if (pr && pr.draft && inputs.skipDraft !== false) {
return util.skip('is draft')
}

if (
inputs.skipKeywords &&
inputs.skipKeywords.length &&
util.hasSkipKeywords(payload.title, inputs.skipKeywords)
) {
return util.skip('title includes skip-keywords')
}

const octokit = util.getOctokit()

const checkIncludeLabels =
const checkIncludings =
inputs.includeLabels != null && inputs.includeLabels.length > 0
const checkExcludeLabels =
const checkExcludings =
inputs.excludeLabels != null && inputs.excludeLabels.length > 0
if (checkIncludings || checkExcludings) {
const labels = await util.getIssueLabels(octokit, payload.number)
const hasAny = (arr: string[]) => labels.some((l) => arr.includes(l))

if (checkIncludeLabels || checkExcludeLabels) {
const labelsRes = await octokit.rest.issues.listLabelsOnIssue({
...context.repo,
issue_number: payload.number,
per_page: 100,
})
const labels = labelsRes.data.map((item) => item.name)
const hasAnyLabel = (inputs: string[]) =>
labels.some((label) => inputs.includes(label))

if (checkIncludeLabels) {
const hasLabels = hasAnyLabel(inputs.includeLabels!)
if (!hasLabels) {
if (checkIncludings) {
const any = hasAny(inputs.includeLabels!)
if (!any) {
return util.skip(`is not labeled with any of the "includeLabels"`)
}
}

if (checkExcludeLabels) {
const hasLabels = hasAnyLabel(inputs.excludeLabels!)
if (hasLabels) {
if (checkExcludings) {
const any = hasAny(inputs.excludeLabels!)
if (any) {
return util.skip(`is labeled with one of the "excludeLabels"`)
}
}
}

const owner = payload.user.login

if (inputs.addReviewers && context.payload.pull_request) {
const { reviewers: candidates, teamReviewers } = util.chooseReviewers(
owner,
inputs,
)
const reviewers: string[] = []
for (let i = 0; i < candidates.length; i++) {
const username = candidates[i]
// eslint-disable-next-line no-await-in-loop
const valid = await util.isValidUser(octokit, username)
if (valid) {
reviewers.push(username)
} else {
core.info(`Ignored unknown reviewer "${username}"`)
}
}

core.info(`Reviewers: ${JSON.stringify(reviewers, null, 2)}`)
core.info(`Team Reviewers: ${JSON.stringify(teamReviewers, null, 2)}`)
if (reviewers.length > 0 || teamReviewers.length > 0) {
await octokit.rest.pulls.requestReviewers({
...context.repo,
reviewers,
team_reviewers: teamReviewers,
pull_number: payload.number,
})
const { assignees, teams, reviewers } = await util.getState(octokit)
if (teams.length || reviewers.length) {
const s = (len: number) => (len > 1 ? 's' : '')
const logTeams = `team_reviewer${s(teams.length)} "${teams.join(', ')}"`
const logReviewers = `reviewer${s(reviewers.length)} "${reviewers.join(
', ',
)}"`

if (teams.length && reviewers.length) {
util.skip(`has requested ${logReviewers} and ${logTeams}`)
} else if (teams.length) {
util.skip(`has requested ${logTeams}`)
} else {
util.skip(`has requested ${logReviewers}`)
}
} else {
await util.addReviewers(octokit, inputs)
}

if (inputs.addAssignees) {
const assignees: string[] = []
const candidates = util.chooseAssignees(owner, inputs)
for (let i = 0; i < candidates.length; i++) {
const username = candidates[i]
// eslint-disable-next-line no-await-in-loop
const valid = await util.isValidUser(octokit, username)
if (valid) {
assignees.push(username)
} else {
core.info(`Ignored unknown assignee "${username}"`)
}
}

core.info(`Assignees: ${JSON.stringify(assignees, null, 2)}`)
if (assignees.length > 0) {
await octokit.rest.issues.addAssignees({
...context.repo,
assignees,
issue_number: payload.number,
})
}
if (assignees.length) {
util.skip(`has assigned to ${assignees.join(', ')}`)
} else {
await util.addAssignees(octokit, inputs)
}
}
} catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion src/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function getInputs() {
return parseInputs({
skipDraft: { type: 'boolean' },
addReviewers: { type: 'boolean', defaultValue: true },
addAssignees: { type: 'booleanOrString', defaultValue: 'author' },
addAssignees: { type: 'boolean', defaultValue: true },
reviewers: { type: 'words' },
assignees: { type: 'words' },
numberOfAssignees: { type: 'int', defaultValue: 0 },
Expand Down
Loading