codeflash-internal/js/cf-api/endpoints/suggest-pr-changes.ts

278 lines
9 KiB
TypeScript
Raw Normal View History

import { determineValidHunks, fileDiffsToMap, isDiffContentsWellFormed } from "../diff_utils"
import { userNickname } from "../auth0-mgmt"
import {
buildDependentPrTitle,
getInstallationOctokitByOwner,
isUserCollaborator,
} from "../github/github-utils"
import { githubApp } from "../github/github-app"
import {
buildResultDetails,
buildResultHeader,
buildResultTestReport,
createDependentPullRequest,
createNewBranchFromDiffContents,
} from "../github/create-pr-from-diffcontents"
import { posthog } from "../analytics"
import suggester from "code-suggester"
import { type FileDiffContent } from "code-suggester/build/src/types"
import { PrismaClient } from "@prisma/client"
import { sendSlackMessage } from "github/slack_util"
const prisma = new PrismaClient()
export async function suggestPrChanges(req, res): Promise<void> {
try {
const {
owner,
repo,
pullNumber,
diffContents,
prCommentFields,
existingTests,
generatedTests,
coverage_message,
} = req.body
const userId = req.userId
//traceId is optional to allow for backwards compatibility, can make this required in the future
const traceId = req.body.traceId || ""
console.log(`traceId: ${traceId}`)
if (!repo || !owner || !pullNumber || !isDiffContentsWellFormed(diffContents)) {
return res.status(400).send("Missing or malformed fields")
}
const nickname = await userNickname(userId)
if (nickname == null) {
return res.status(401).send("Unauthorized") // Error getting user nickname
}
const installationOctokit = await getInstallationOctokitByOwner(githubApp, owner, repo)
if (installationOctokit instanceof Error) {
return res.status(401).send(installationOctokit.message)
}
const isCollaborator = await isUserCollaborator(installationOctokit, owner, repo, nickname)
if (!isCollaborator) {
console.log(`${nickname} is not a collaborator on ${owner}/${repo}`)
return res.status(401).send("Unauthorized") // User is not a collaborator
}
console.log(`${nickname} is a collaborator on ${owner}/${repo}`)
// Suggest changes
const diffContentsMap: Map<string, FileDiffContent> = fileDiffsToMap(diffContents)
const { validHunks, invalidHunks } = await determineValidHunks(
installationOctokit.rest,
{ owner, repo },
pullNumber,
100,
diffContentsMap,
)
if (invalidHunks.size > 0) {
// we can't suggest all of the hunks for this PR, because some of them are invalid (out of scope for this PR).
// so instead, let's make a new branch, commit the changes,
// and make a new PR from that branch onto the PR's branch
console.log(
`Can't suggest all of the hunks for this PR, because some of them are invalid (out of scope for this PR).`,
)
console.log(`Making a new dependent PR...`)
// a timestamp format like 2024-01-31-12.59.48
const timestamp = new Date()
.toISOString()
.replace(/:/g, ".")
.replace(/\.\d+Z$/, "")
// If you change this, please also change the regex in github-app.ts in the pull_request.closed event
const newBranchName = `codeflash/optimize-pr${pullNumber}-${timestamp}`
// Get the head branch of the original pull request
const originalPrData = await installationOctokit.rest.pulls.get({
owner,
repo,
pull_number: pullNumber,
})
const baseBranch = originalPrData.data.head.ref
const commitMessage =
buildDependentPrTitle(
prCommentFields.function_name,
prCommentFields.speedup_pct,
pullNumber,
baseBranch,
) + `\n${prCommentFields.optimization_explanation}`
const branchCreated = await createNewBranchFromDiffContents(
installationOctokit,
owner,
repo,
newBranchName,
baseBranch,
diffContentsMap,
commitMessage,
)
if (!branchCreated) {
throw new Error(`Failed to create branch ${newBranchName}`)
}
const newPrData = await createDependentPullRequest(
installationOctokit,
owner,
repo,
pullNumber,
newBranchName,
baseBranch,
prCommentFields,
existingTests,
generatedTests,
)
// Respond with the new PR details
console.log(
`Created new dependent PR #${newPrData.data.number} from branch ${newPrData.data.head.ref}`,
)
2024-11-15 00:02:03 +00:00
if (owner === "Future-House" && (repo === "aviary" || repo === "paper-qa")) {
2024-11-14 23:58:34 +00:00
await sendSlackMessage(
`new dependent PR created: ${newPrData.data.html_url} for ${owner}/${repo}`,
)
}
2024-11-14 23:58:34 +00:00
posthog.capture({
distinctId: userId,
event: `cfapi-suggest-pr-changes-success-dependent-pr-created`,
properties: {
owner,
repo,
newPrNumber: newPrData.data.number,
newPrBranch: newPrData.data.head.ref,
2024-06-07 19:26:05 +00:00
PRURL: newPrData.data.html_url,
},
})
if (traceId !== "") {
let pull_request_db = await prisma.optimization_features.findUnique({
where: {
trace_id: traceId,
},
select: {
pull_request: true,
},
})
2024-07-20 05:15:57 +00:00
if (pull_request_db) {
if (pull_request_db.pull_request === null || pull_request_db.pull_request === undefined) {
pull_request_db.pull_request = {}
}
pull_request_db.pull_request.dependent_pr_url = newPrData.data.html_url
await prisma.optimization_features.update({
where: {
trace_id: traceId,
},
data: pull_request_db,
})
}
}
res.json(newPrData.data.number)
} else {
// all suggestions are valid and can be made on this PR.
// let's make the suggestions
console.log(`All suggestions are valid and can be made on this PR.`)
console.log(`Making suggestions...`)
const reviewNumber = await suggester.reviewPullRequest(
installationOctokit.rest,
diffContentsMap,
{
repo,
owner,
pullNumber,
pageSize: 100,
},
)
if (reviewNumber == null) {
console.log(`Failed to create a suggester review for PR #${pullNumber}.`)
console.log(`This may be because there are no changes to be made.`)
console.log(`diffContentsMap: ${JSON.stringify(diffContentsMap)}`)
posthog.capture({
distinctId: userId,
event: `cfapi-suggest-pr-changes-failed-no-changes`,
})
return res.status(500).send("Failed creating suggestions for review.")
}
console.log(`Created suggestions for review #${reviewNumber} for PR #${pullNumber}`)
const prCommentBody =
`## ⚡️ Codeflash found optimizations for this PR\n` +
`${buildResultHeader(prCommentFields)}\n` +
`${buildResultDetails(prCommentFields)}\n` +
`${buildResultTestReport(prCommentFields, existingTests, generatedTests, coverage_message)}\n`
// Add explanation comment
const result = await installationOctokit.rest.issues.createComment({
owner,
repo,
issue_number: pullNumber,
body: prCommentBody,
})
console.log(`Added review comment to PR #${pullNumber}: ${result.data.html_url}`)
2024-11-15 00:02:03 +00:00
if (owner === "Future-House" && (repo === "aviary" || repo === "paper-qa")) {
await sendSlackMessage(`Suggestions made for PR #${pullNumber} in ${owner}/${repo}`)
}
posthog.capture({
distinctId: userId,
event: `cfapi-suggest-pr-changes-success-suggestions-made`,
properties: {
owner,
repo,
reviewNumber,
2024-06-07 19:26:05 +00:00
PRURL: result.data.html_url,
},
})
if (traceId !== "") {
let pull_request_db = await prisma.optimization_features.findUnique({
where: {
trace_id: traceId,
},
select: {
pull_request: true,
},
})
2024-07-20 05:15:57 +00:00
if (pull_request_db) {
// the trace_id is not in the database then ignore it, because it should already exist by this stage.
2024-07-20 05:15:57 +00:00
if (pull_request_db.pull_request === null || pull_request_db.pull_request === undefined) {
pull_request_db.pull_request = {}
}
2024-08-13 04:38:07 +00:00
pull_request_db.pull_request.review_suggestion_pr_url = result.data.html_url
await prisma.optimization_features.update({
where: {
trace_id: traceId,
},
data: pull_request_db,
})
}
}
res.json(reviewNumber)
}
} catch (error: any) {
console.log(`Error in /cfapi/suggest-pr-changes: ${error}`)
console.log(`Error message: ${error.message}`)
console.log(`Error stack: ${error.stack}`)
posthog.capture({
distinctId: req.userId,
event: `cfapi-suggest-pr-changes-failed-error`,
properties: {
error: error.message,
stack: error.stack,
},
})
res.status(500).send(`Error creating pull request: ${error.message}`)
}
}