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

Unique pr comment #512

Merged
merged 12 commits into from
Jun 5, 2024
Merged
4 changes: 3 additions & 1 deletion .github/workflows/build-genai.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ on:
- "packages/core/**"
- "packages/sample/**"
- "packages/cli/**"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ on:
branches: [main]
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
with:
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/genai-alt-text.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ on:
pull_request:
paths:
- "docs/src/**/*.png"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/genai-frontmatter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
paths:
- "docs/src/**/*.md"
- "docs/src/**/*.mdx"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/genai-pr-review.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
name: genai pull request review
on:
pull_request:
types: [ready_for_review]
paths:
- yarn.lock
- ".github/workflows/ollama.yml"
- "packages/core/**/*"
- "packages/cli/**/*"
- "packages/samples/**/*"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ollama.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ on:
- "packages/core/**/*"
- "packages/cli/**/*"
- "packages/samples/**/*"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
runs-on: ubuntu-latest
Expand Down
5 changes: 2 additions & 3 deletions docs/src/content/docs/reference/cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ Options:
-od, --out-data <string> output file for data (.jsonl/ndjson will be aggregated). JSON schema information and validation will be included if available.
-oa, --out-annotations <string> output file for annotations (.csv will be rendered as csv, .jsonl/ndjson will be aggregated)
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
-ocl, --out-changelog <string> output file for changelogs
-prc, --pull-request-comment [string] create comment on a pull request.
-prd, --pull-request-description [string] upsert comment on a pull request description.
-prc, --pull-request-comment [string] create comment on a pull request with a unique id (defaults to script id)
-prd, --pull-request-description [string] create comment on a pull request description with a unique id (defaults to script id)
-prr, --pull-request-reviews create pull request reviews from annotations
--no-pull-request-reviews-cache disable pull request reviews cache
-j, --json emit full JSON response to output
-y, --yaml emit full YAML response to output
-p, --prompt dry run, don't execute LLM and return expanded prompt
Expand Down
8 changes: 2 additions & 6 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,16 @@ export async function cli() {
.option("-ocl, --out-changelog <string>", "output file for changelogs")
.option(
"-prc, --pull-request-comment [string]",
"create comment on a pull request."
"create comment on a pull request with a unique id (defaults to script id)"
)
.option(
"-prd, --pull-request-description [string]",
"upsert comment on a pull request description."
"create comment on a pull request description with a unique id (defaults to script id)"
)
.option(
"-prr, --pull-request-reviews",
"create pull request reviews from annotations"
)
.option(
"--no-pull-request-reviews-cache",
"disable pull request reviews cache"
)
.option("-j, --json", "emit full JSON response to output")
.option("-y, --yaml", "emit full YAML response to output")
.option(
Expand Down
23 changes: 3 additions & 20 deletions packages/cli/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,9 @@ import {
CSV_REGEX,
CLI_RUN_FILES_FOLDER,
parseGHTokenFromEnv,
githubUpsetPullRequest,
githubUpdatePullRequestDescription,
githubCreatePullRequestReviews,
githubCreateIssueComment,
GITHUB_PULL_REQUEST_REVIEWS_CACHE,
JSONLineCache,
PullRequestReviewsCacheKey,
PullRequestReviewsCacheValue,
} from "genaiscript-core"
import { capitalize } from "inflection"
import { basename, resolve, join, relative } from "node:path"
Expand Down Expand Up @@ -66,7 +62,6 @@ export async function runScript(
pullRequestComment: string | boolean
pullRequestDescription: string | boolean
pullRequestReviews: boolean
pullRequestReviewsCache: boolean
outData: string
label: string
temperature: string
Expand Down Expand Up @@ -106,7 +101,6 @@ export async function runScript(
const maxTokens = normalizeInt(options.maxTokens)
const maxToolCalls = normalizeInt(options.maxToolCalls)
const cache = !!options.cache
const pullRequestReviewsCache = !!options.pullRequestReviewsCache
const applyEdits = !!options.applyEdits
const csvSeparator = options.csvSeparator || "\t"
const removeOut = options.removeOut
Expand Down Expand Up @@ -375,18 +369,7 @@ ${Array.from(files)
if (pullRequestReviews && res.annotations?.length) {
const info = parseGHTokenFromEnv(process.env)
if (info.repository && info.issue) {
const cache = pullRequestReviewsCache
? JSONLineCache.byName<
PullRequestReviewsCacheKey,
PullRequestReviewsCacheValue
>(GITHUB_PULL_REQUEST_REVIEWS_CACHE)
: undefined
await githubCreatePullRequestReviews(
script,
info,
res.annotations,
{ cache }
)
await githubCreatePullRequestReviews(script, info, res.annotations)
}
}
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
pelikhan marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -407,7 +390,7 @@ ${Array.from(files)
if (pullRequestDescription && res.text) {
const info = parseGHTokenFromEnv(process.env)
if (info.repository && info.issue) {
await githubUpsetPullRequest(
await githubUpdatePullRequestDescription(
script,
info,
res.text,
Expand Down
136 changes: 67 additions & 69 deletions packages/core/src/github.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { assert } from "node:console"
import {
GITHUB_API_VERSION,
GITHUB_PULL_REQUEST_REVIEWS_CACHE,
GITHUB_TOKEN,
} from "./constants"
import { GITHUB_API_VERSION, GITHUB_TOKEN } from "./constants"
import { createFetch } from "./fetch"
import { host } from "./host"
import { link, prettifyMarkdown } from "./markdown"
import { logError, logVerbose, normalizeInt } from "./util"
import { string } from "mathjs"
import { JSONLineCache } from "./cache"

export interface GithubConnectionInfo {
token: string
Expand Down Expand Up @@ -61,13 +55,14 @@ export function parseGHTokenFromEnv(
}

// https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#update-a-pull-request
export async function githubUpsetPullRequest(
export async function githubUpdatePullRequestDescription(
script: PromptScript,
info: GithubConnectionInfo,
text: string,
commentTag: string
) {
const { apiUrl, repository, issue } = info
assert(commentTag)

if (!issue) return { updated: false, statusText: "missing issue number" }
const token = await host.readSecret(GITHUB_TOKEN)
Expand All @@ -89,15 +84,21 @@ export async function githubUpsetPullRequest(
const resGetJson = (await resGet.json()) as { body: string }
let { body } = resGetJson
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
if (!body) body = ""
const tag = `\n\n<!-- genaiscript begin ${commentTag} -->\n\n`
const endTag = `\n\n<!-- genaiscript end ${commentTag} -->\n\n`
const tag = `<!-- genaiscript begin ${commentTag} -->`
const endTag = `<!-- genaiscript end ${commentTag} -->`
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
const sep = "\n\n"

const start = body.indexOf(tag)
const end = body.indexOf(endTag)
if (start > -1 && end > -1 && start < end) {
body = body.slice(0, start + tag.length) + text + body.slice(end)
body =
body.slice(0, start + tag.length) +
sep +
text +
sep +
body.slice(end)
} else {
body = body + tag + text + endTag
body = body + sep + tag + sep + text + sep + endTag + sep
}

const res = await fetch(url, {
Expand Down Expand Up @@ -156,20 +157,23 @@ export async function githubCreateIssueComment(
const tag = `<!-- genaiscript ${commentTag} -->`
body = `${body}\n\n${tag}\n\n`
// try to find the existing comment
const resListComments = await fetch(`${url}?per_page=100`, {
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${token}`,
"X-GitHub-Api-Version": GITHUB_API_VERSION,
},
})
const resListComments = await fetch(
`${url}?per_page=100&sort=updated`,
{
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${token}`,
"X-GitHub-Api-Version": GITHUB_API_VERSION,
},
}
)
if (resListComments.status !== 200)
return { created: false, statusText: resListComments.statusText }
const comments = (await resListComments.json()) as {
id: string
body: string
}[]

console.log(comments)
const comment = comments.find((c) => c.body.includes(tag))
if (comment) {
const delurl = `${apiUrl}/repos/${repository}/issues/comments/${comment.id}`
Expand Down Expand Up @@ -213,20 +217,34 @@ async function githubCreatePullRequestReview(
script: PromptScript,
info: GithubConnectionInfo,
token: string,
annotation: Diagnostic
annotation: Diagnostic,
existingComments: { id: string; path: string; line: number; body: string }[]
) {
assert(token)
const { apiUrl, repository, issue, commitSha } = info

const fetch = await createFetch({ retryOn: [] })
const url = `${apiUrl}/repos/${repository}/pulls/${issue}/comments`
const body = {
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
body: appendGeneratedComment(script, info, annotation.message),
commit_id: commitSha,
path: annotation.filename,
line: annotation.range?.[0]?.[0],
side: "RIGHT",
}
if (
existingComments.find(
(c) =>
c.path === body.path &&
c.line === body.line &&
c.body === body.body
)
) {
logVerbose(
`pull request ${commitSha} comment creation already exists, skipping`
)
return { created: false, statusText: "comment already exists" }
}
const fetch = await createFetch({ retryOn: [] })
const url = `${apiUrl}/repos/${repository}/pulls/${issue}/comments`
const res = await fetch(url, {
method: "POST",
headers: {
Expand All @@ -251,36 +269,12 @@ async function githubCreatePullRequestReview(
return r
}

export interface PullRequestReviewsCacheKey {
repository: string
issue: number
scriptId: string
message: string
filename: string
line: number
}

export interface PullRequestReviewsCacheValue {
created: boolean
statusText: string
html_url: string
}

export type PullRequestReviewsCache = JSONLineCache<
PullRequestReviewsCacheKey,
PullRequestReviewsCacheValue
>

export async function githubCreatePullRequestReviews(
script: PromptScript,
info: GithubConnectionInfo,
annotations: Diagnostic[],
options?: {
cache?: PullRequestReviewsCache
}
annotations: Diagnostic[]
): Promise<boolean> {
const { repository, issue, sha } = info
const { cache } = options || {}
const { repository, issue, sha, apiUrl } = info

if (!annotations?.length) return true
if (!issue) {
Expand All @@ -297,28 +291,32 @@ export async function githubCreatePullRequestReviews(
return false
}

// query existing reviews
const fetch = await createFetch({ retryOn: [] })
const url = `${apiUrl}/repos/${repository}/pulls/${issue}/comments`
const resListComments = await fetch(`${url}?per_page=100&sort=updated`, {
headers: {
Accept: "application/vnd.github+json",
pelikhan marked this conversation as resolved.
Show resolved Hide resolved
Authorization: `Bearer ${token}`,
"X-GitHub-Api-Version": GITHUB_API_VERSION,
},
})
if (resListComments.status !== 200) return false
const comments = (await resListComments.json()) as {
id: string
path: string
line: number
body: string
}[]
// code annotations
for (const annotation of annotations) {
const cacheKey = {
repository,
issue,
scriptId: script.id,
message: annotation.message,
filename: annotation.filename,
line: annotation.range?.[0]?.[0],
}
const cached = await cache?.get(cacheKey)
if (cached)
logVerbose("ignore cached pull request review, " + cached.html_url)
else {
const res = await githubCreatePullRequestReview(
script,
info,
token,
annotation
)
if (res.created) await cache?.set(cacheKey, res)
}
await githubCreatePullRequestReview(
script,
info,
token,
annotation,
comments
)
}
return true
}
Loading