Skip to content

Commit c4df425

Browse files
committed
Merge branch 'main' into v2
2 parents 928c52a + 6e468b2 commit c4df425

File tree

23 files changed

+458
-22
lines changed

23 files changed

+458
-22
lines changed

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ Codeball is a code review AI which approves Pull Requests that a human would hav
99

1010
The AI identifies and approves safe contributions, so that you get to focus your energy on the tricky ones.
1111

12+
* Identifies and **approves** safe contributions
13+
* _[beta]_ Generates **code suggestions** from comments ([read more](https://codeball.ai/suggester))
14+
1215
## GitHub Action
1316

1417
The Codeball GitHub Action runs [Codeball](https://codeball.ai/) on all new Pull Requests, and approves the Pull Request ([example](https://github.com/sturdy-dev/codeball-action/pull/7)) if the model classifies it as safe.
@@ -22,7 +25,10 @@ The Codeball GitHub Action runs [Codeball](https://codeball.ai/) on all new Pull
2225

2326
```yaml
2427
name: Codeball
25-
on: [pull_request]
28+
on:
29+
pull_request: {}
30+
pull_request_review_comment:
31+
types: [created, edited]
2632

2733
jobs:
2834
codeball_job:
@@ -36,6 +42,7 @@ jobs:
3642
labelPullRequestsWhenApproved: "true"
3743
labelPullRequestsWhenReviewNeeded: "false"
3844
failJobsWhenReviewNeeded: "false"
45+
codeSuggestionsFromComments: "true"
3946
```
4047
4148
2. 🎉 That's it! Codeball will now run on new Pull Requests, and will approve the PR if it's a good one!
@@ -184,8 +191,9 @@ The Codeball sub-actions are:
184191
185192
* [`sturdy-dev/codeball-action/baller/@v2`](./baller/README.md) – Triggers new Codeball Jobs
186193
* [`sturdy-dev/codeball-action/status/@v2`](./status/README.md) – Waits for the the Codeball result
187-
* [`sturdy-dev/codeball-action/approver/@v2`](./approver/README.md) – Approves PRs
194+
* [`sturdy-dev/codeball-action/approver/@v2`](./approver/README.md) - Approves PRs
188195
* [`sturdy-dev/codeball-action/labeler/@v2`](./labeler/README.md) – Adds labels to PRs
196+
* [`sturdy-dev/codeball-action/suggester/@v2`](./suggester/README.md) – Converts comments to code suggestions
189197

190198
## How Codeball works
191199

@@ -213,6 +221,18 @@ permissions:
213221
pull-requests: write
214222
```
215223

224+
To allow PR approvals, make sure that **"Allow GitHub Actions to Create and Approve Pull Requests"** is enabled in the repository and organization settings on GitHub (under "Settings > Actions > General").
225+
226+
<details>
227+
<summary>Show recommended GitHub Permissions</summary>
228+
229+
![Fork pull request workflows from outside collaborators](https://user-images.githubusercontent.com/47952/184130867-8c149bfa-e827-425c-882b-eacf775c9542.png)
230+
![Fork pull request workflows in private repositories](https://user-images.githubusercontent.com/47952/184130872-7e91445d-4287-4b80-8c3b-6ff40fc893db.png)
231+
![Workflow permissions](https://user-images.githubusercontent.com/47952/184130874-54458e54-84f4-48fb-9347-0188c3ba27b6.png)
232+
</details>
233+
234+
If you can not (or do not want to) update the org and repo settings for GitHub Actions, install the ["Codeball AI Writer"](https://github.com/apps/codeball-ai-writer) GitHub App on the repository. When installed, Codeball will use the permissions granted via the app instead of the GitHub Actions token.
235+
216236
### Forks and public repositories
217237

218238
GitHub does not offer (and reasonably so) a way for Pull Requests from a fork to a public repository to run with "write" permissions to the parent repository.

action.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ inputs:
2424
description: 'If "true", the action will exit with status code 1 if the Codeball AI does not approve the contribution'
2525
default: "false"
2626
required: false
27+
codeSuggestionsFromComments:
28+
description: 'If "true", Codeball will read generate code suggestions from comments made in Pull Requests'
29+
default: "false"
30+
required: false
2731

2832
runs:
2933
using: 'composite'
@@ -45,7 +49,7 @@ runs:
4549
# If Codeball approved the contribution, add a "codeball:approved" label
4650
- name: Label Approved
4751
uses: sturdy-dev/codeball-action/labeler@v2
48-
if: ${{ steps.codeball_status.outputs.approved == 'true' && inputs.labelPullRequestsWhenApproved == 'true' }}
52+
if: ${{ steps.codeball_status.outputs.approved == 'true' && inputs.labelPullRequestsWhenApproved == 'true' && steps.codeball_status.outputs.jobType == 'contribution' }}
4953
with:
5054
name: "codeball:approved"
5155
color: "86efac" # green
@@ -55,7 +59,7 @@ runs:
5559
# If Codeball did not approve the contribution, add a "codeball:needs-review" label
5660
- name: Label Needs Review
5761
uses: sturdy-dev/codeball-action/labeler@v2
58-
if: ${{ steps.codeball_status.outputs.approved == 'false' && inputs.labelPullRequestsWhenReviewNeeded == 'true' }}
62+
if: ${{ steps.codeball_status.outputs.approved == 'false' && inputs.labelPullRequestsWhenReviewNeeded == 'true' && steps.codeball_status.outputs.jobType == 'contribution' }}
5963
with:
6064
name: "codeball:needs-review"
6165
color: "bfdbfe" # blue
@@ -65,14 +69,21 @@ runs:
6569
# If Codeball approved the contribution, approve the PR
6670
- name: Approve PR
6771
uses: sturdy-dev/codeball-action/approver@v2
68-
if: ${{ steps.codeball_status.outputs.approved == 'true' && inputs.approvePullRequests == 'true' }}
72+
if: ${{ steps.codeball_status.outputs.approved == 'true' && inputs.approvePullRequests == 'true' && steps.codeball_status.outputs.jobType == 'contribution' }}
73+
with:
74+
codeball-job-id: ${{ steps.codeball_baller.outputs.codeball-job-id }}
75+
76+
# If Codeball have code suggestions, add suggestions as comments
77+
- name: Add Suggestions
78+
uses: sturdy-dev/codeball-action/suggester@v2
79+
if: ${{ steps.codeball_status.outputs.suggested == 'true' && inputs.codeSuggestionsFromComments == 'true' && steps.codeball_status.outputs.jobType == 'comment' }}
6980
with:
7081
codeball-job-id: ${{ steps.codeball_baller.outputs.codeball-job-id }}
7182

7283
# If Codeball didn't approve the contribution, fail the job.
7384
- name: Fail Job
7485
shell: bash
75-
if: ${{ steps.codeball_status.outputs.approved == 'false' && inputs.failJobsWhenReviewNeeded == 'true' }}
86+
if: ${{ steps.codeball_status.outputs.approved == 'false' && inputs.failJobsWhenReviewNeeded == 'true' && steps.codeball_status.outputs.jobType == 'contribution' }}
7687
run: |
7788
echo "Not approved"
7889
exit 1

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
"description": "",
55
"main": "lib/main.js",
66
"scripts": {
7-
"build": "yarn baller:build && yarn approver:build && yarn status:build && yarn labeler:build",
7+
"build": "yarn baller:build && yarn approver:build && yarn status:build && yarn labeler:build && yarn suggester:build",
88
"format": "prettier --write '**/*.ts'",
99
"format-check": "prettier --check '**/*.ts'",
1010
"lint": "eslint src/**/*.ts",
1111
"baller:build": "ncc build src/baller/main.ts --out dist/baller --source-map --license licenses.txt",
1212
"approver:build": "ncc build src/approver/main.ts --out dist/approver --source-map --license licenses.txt",
1313
"status:build": "ncc build src/status/main.ts --out dist/status --source-map --license licenses.txt",
1414
"labeler:build": "ncc build src/labeler/main.ts --out dist/labeler --source-map --license licenses.txt",
15+
"suggester:build": "ncc build src/suggester/main.ts --out dist/suggester --source-map --license licenses.txt",
1516
"test": "jest",
1617
"all": "yarn format && yarn lint && yarn build"
1718
},

src/approver/main.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as core from '@actions/core'
22
import * as github from '@actions/github'
3-
import {Octokit, optional, required} from '../lib'
3+
import {features, Octokit, optional, required} from '../lib'
44
import {ForbiddenError} from '../lib/api'
55
import {approve} from '../lib/github'
66
import {track} from '../lib/track'
@@ -59,14 +59,23 @@ async function run(): Promise<void> {
5959
const isFromFork = pr.head.repo?.fork
6060
const isToFork = pr.base.repo.fork
6161

62+
const feats = await features({jobID})
63+
64+
if (!feats.approve) {
65+
core.error(
66+
'Unable to run this action as the feature is not available for your organization. Please upgrade your Codeball plan, or contact support@codeball.ai'
67+
)
68+
return
69+
}
70+
6271
await octokit.pulls
6372
.createReview({
6473
owner: repoOwner,
6574
repo: repoName,
6675
pull_number: pullRequestNumber,
6776
commit_id: commitId,
6877
body: reviewMessage,
69-
event: 'APPROVE'
78+
event: feats.approve ? 'APPROVE' : 'COMMENT'
7079
})
7180
.catch(async error => {
7281
if (

src/baller/main.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ async function run(): Promise<{jobId: string}> {
77
const pullRequestURL = github.context.payload?.pull_request?.html_url
88
if (!pullRequestURL) throw new Error('No pull request URL found')
99

10+
const commentURL = github.context.payload?.comment?.html_url
11+
1012
const githubToken = core.getInput('GITHUB_TOKEN')
1113
if (!githubToken) throw new Error('No GitHub token found')
1214

1315
core.info(`Found contribution: ${pullRequestURL}`)
16+
if (commentURL) core.info(`Found comment: ${commentURL}`)
1417

1518
const job = await create({
16-
url: pullRequestURL,
19+
// if commentURL is present, we are in the context of a comment action, so trigger that.
20+
url: commentURL ?? pullRequestURL,
1721
access_token: githubToken
1822
})
1923

src/labeler/main.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as core from '@actions/core'
22
import * as github from '@actions/github'
3-
import {Octokit, optional, required} from '../lib'
3+
import {Octokit, optional, required, features} from '../lib'
44
import {label as labelViaAPI} from '../lib/github'
55
import {ForbiddenError} from '../lib/api'
66
import {track} from '../lib/track'
@@ -117,6 +117,14 @@ const run = async (): Promise<void> => {
117117
const labelDescription = required('description')
118118
const removeLabelNames = optional('remove-label-names')
119119

120+
const feats = await features({jobID})
121+
if (!feats.label) {
122+
core.error(
123+
'Unable to run this action as the feature is not available for your organization. Please upgrade your Codeball plan, or contact support@codeball.ai'
124+
)
125+
return
126+
}
127+
120128
const pr = await octokit.pulls
121129
.get({
122130
owner: repoOwner,
@@ -155,8 +163,8 @@ const run = async (): Promise<void> => {
155163
if (error.name === ForbiddenError.name) {
156164
throw new Error(
157165
!isPrivate && isFromFork && !isToFork
158-
? 'Codeball Labler failed to access GitHub. Install https://github.com/apps/codeball-ai-writer to the base repository to give Codeball permission to label Pull Requests.'
159-
: 'Codeball Labler failed to access GitHub. Check the "GITHUB_TOKEN Permissions" of this job and make sure that the job has WRITE permissions to Pull Requests.'
166+
? 'Codeball Labeler failed to access GitHub. Install https://github.com/apps/codeball-ai-writer to the base repository to give Codeball permission to label Pull Requests.'
167+
: 'Codeball Labeler failed to access GitHub. Check the "GITHUB_TOKEN Permissions" of this job and make sure that the job has WRITE permissions to Pull Requests.'
160168
)
161169
}
162170
throw error

src/lib/api/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ const handleResponse = async (response: Response): Promise<any> => {
3737
}
3838
}
3939

40-
export const get = async (path: string) =>
41-
fetch(new URL(path, BASE_URL).toString(), {
40+
export const get = async (path: string, params = new URLSearchParams()) =>
41+
fetch(new URL(path, BASE_URL).toString() + `?${params.toString()}}`, {
4242
headers: {
4343
'User-Agent': 'github-actions'
4444
},

src/lib/eq/eq.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {eq} from './eq'
2+
3+
describe('EQ', () => {
4+
test('EQ', () => {
5+
expect(eq(undefined, undefined)).toBeTruthy()
6+
expect(eq(undefined, null)).toBeTruthy()
7+
expect(eq(null, undefined)).toBeTruthy()
8+
expect(eq(null, null)).toBeTruthy()
9+
expect(eq(1, 1)).toBeTruthy()
10+
expect(eq(1, 2)).toBeFalsy()
11+
expect(eq(2, 1)).toBeFalsy()
12+
expect(eq(1, '1')).toBeFalsy()
13+
})
14+
})

src/lib/eq/eq.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// eq is an equality check, but treats null and undefined as equal
2+
export const eq = (
3+
a: number | undefined | null | string,
4+
b: number | undefined | null | string
5+
): boolean => {
6+
if (a === null) {
7+
a = undefined
8+
}
9+
if (b === null) {
10+
b = undefined
11+
}
12+
return a === b
13+
}

src/lib/eq/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './eq'

0 commit comments

Comments
 (0)