-
Notifications
You must be signed in to change notification settings - Fork 7
feat: Github App For Keploy (Beta) #9
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
00bb3aa
f8f5aa8
2865040
0bd2663
0d8de5c
0674256
8040006
aec11b4
4ac4230
161ebcd
d0cd28f
817708e
8bfd0a1
518bc31
d1c86ed
31dbc3f
de71c91
7628857
e8f9aec
fd313b9
27192ee
ce2c164
76fc8ae
b0d8d32
0a1126c
0f3a9a0
5f0f1ed
29f7c3a
6c0de73
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
name: Run test-cases | ||
|
||
on: | ||
pull_request: | ||
types: [opened, synchronize] | ||
|
||
jobs: | ||
my_job: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Keploy Report | ||
uses: keploy/testgpt@main | ||
with: | ||
working-directory: ${{ github.workspace }} | ||
delay: 10 | ||
command: node src/app.js | ||
keploy-path: . |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
node_modules | ||
npm-debug.log | ||
*.pem | ||
!mock-cert.pem | ||
.env | ||
coverage | ||
lib | ||
scripts/* | ||
/dist | ||
|
||
myenv/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// diffParser.ts | ||
import parseDiff from 'parse-diff'; | ||
|
||
|
||
async function parseGitDiffFromLLMOutput(llmOutput: any) { | ||
const diffStart = llmOutput.indexOf('```diff'); | ||
const diffEnd = llmOutput.indexOf('```', diffStart + 1); | ||
return llmOutput.substring(diffStart, diffEnd); | ||
} | ||
|
||
|
||
export async function reviewPR(context: any, app: any, llmOutput: any) { | ||
// export async function reviewPR(context: any, app: any) { | ||
//trim the llmOutput to only include the diff | ||
const ifLGTM = llmOutput.includes('LGTM'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use regex for exact match to avoid false positive There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The relevant Regex has been added! |
||
if (ifLGTM) { | ||
await context.octokit.issues.createComment({ | ||
...context.repo(), | ||
issue_number: context.payload.pull_request.number, | ||
body: 'LGTM: LLM analysis is successful' | ||
}); | ||
return; | ||
} | ||
const gitDiff = await parseGitDiffFromLLMOutput(llmOutput); | ||
// const gitDiff = `diff --git a/src/index.js b/src/index.js | ||
// index abc1234..def5678 100644 | ||
// --- a/src/index.js | ||
// +++ b/src/index.js | ||
// @@ -1,5 +1,5 @@ | ||
// function add(a, b) { | ||
// - return a - b; // Bug: Subtraction instead of addition | ||
// + return a + b; // Fixed: Now correctly adds | ||
// } | ||
|
||
// function subtract(a, b) { | ||
// @@ -10,7 +10,7 @@ function subtract(a, b) { | ||
// function multiply(a, b) { | ||
// return a * b; | ||
// } | ||
|
||
// -function divide(a, b) { | ||
// - return a / b; | ||
// +function divide(a, b) { | ||
// + return b !== 0 ? a / b : NaN; // Added check for division by zero | ||
// } | ||
// diff --git a/tests/test.js b/tests/test.js | ||
// index 1234567..890abcd 100644 | ||
// --- a/tests/test.js | ||
// +++ b/tests/test.js | ||
// @@ -5,6 +5,6 @@ describe('Math operations', () => { | ||
// expect(add(2, 3)).toBe(5); | ||
// }); | ||
|
||
// - test('subtract should return the difference', () => { | ||
// + test('subtract should return the correct difference', () => { | ||
// expect(subtract(5, 3)).toBe(2); | ||
// }); | ||
// }); | ||
// `; | ||
// Create inline comments from the diff | ||
await createInlineCommentsFromDiff(gitDiff, context, app); | ||
|
||
// Post the LLM analysis as a comment | ||
await context.octokit.issues.createComment({ | ||
...context.repo(), | ||
issue_number: context.payload.pull_request.number, | ||
body: llmOutput, | ||
}); | ||
} | ||
|
||
|
||
export async function createInlineCommentsFromDiff(diff: string, context: any, app: any) { | ||
const parsedFiles = parseDiff(diff); | ||
const { pull_request, repository } = context.payload; | ||
|
||
for (const file of parsedFiles) { | ||
if (file.to === '/dev/null') { | ||
app.log.info(`Skipping deleted file: ${file.from}`); | ||
continue; | ||
} | ||
|
||
const filePath = file.to || file.from; | ||
|
||
for (const chunk of file.chunks) { | ||
for (const change of chunk.changes) { | ||
if (change.type !== 'add') continue; // Focus on additions for comments | ||
|
||
const line = change.ln; // Line number in the new file | ||
const content = change.content.slice(1).trim(); | ||
const body = `Suggested change:\n\`\`\`suggestion\n${content}\n\`\`\``; | ||
|
||
try { | ||
await context.octokit.pulls.createReviewComment({ | ||
owner: repository.owner.login, | ||
repo: repository.name, | ||
pull_number: pull_request.number, | ||
commit_id: pull_request.head.sha, | ||
path: filePath, | ||
body, | ||
line, | ||
mediaType: { | ||
previews: ['comfort-fade'], // Enable comfort-fade preview | ||
}, | ||
}); | ||
app.log.info(`Created comment on ${filePath} line ${line}`); | ||
} catch (error: any) { | ||
app.log.error( | ||
`Failed to create comment for ${filePath} line ${line}: ${error.message}` | ||
); | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Deployments API example | ||
// See: https://developer.github.com/v3/repos/deployments/ to learn more | ||
|
||
/** | ||
* This is the main entrypoint to your Probot app | ||
* @param {import('probot').Probot} app | ||
*/ | ||
import { getAllPrDetails } from "./pr.js"; | ||
import { handlePrAnalysis } from "./llm.js"; | ||
import { handleKeployWorkflowTrigger } from "./keploy.js"; | ||
import { handleError } from "./utils.js"; | ||
import { handleSecurityWorkflowTrigger } from "./security.js"; | ||
import { promptUserConfig } from './src/cli.js'; | ||
import { reviewPR } from './diffparser.js'; | ||
import { handleLintWorkflowTrigger } from "./lint.js"; | ||
|
||
let config: any; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please avoid using any There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Their existence has been perished! |
||
|
||
export default async (app: { | ||
log: { info: (arg0: string, arg1?: string) => void }; | ||
on: (arg0: string[], arg1: (context: any) => Promise<void>) => void; | ||
}) => { | ||
try { | ||
// Get user configuration through CLI | ||
config = await promptUserConfig(); | ||
// selectedModel = config.model; | ||
app.log.info(`Initialized with API url: ${config.apiEndpoint} for use case: ${config.useCase} and model : ${config.selectedModel}`); | ||
} catch (error) { | ||
app.log.info("Failed to get user configuration"); | ||
process.exit(1); | ||
} | ||
|
||
app.log.info("Yay, the app was loaded!"); | ||
|
||
const handlePrEvent = async (context: any) => { | ||
try { | ||
const prData = await getAllPrDetails(context, app); | ||
app.log.info(JSON.stringify(prData), "Full PR data collected"); | ||
|
||
const llmOutput = await handlePrAnalysis(context, prData , config.apiEndpoint , config.selectedModel, app); | ||
// const stringllmOutput = await JSON.stringify(llmOutput); | ||
// app.log.info(JSON.stringify(stringllmOutput), "LLM analysis complete"); | ||
await reviewPR(context, app, llmOutput); | ||
// await reviewPR(context, app); | ||
|
||
await handleKeployWorkflowTrigger(context); | ||
await handleSecurityWorkflowTrigger(context); | ||
await handleLintWorkflowTrigger(context); | ||
} catch (error) { | ||
await handleError(context, app, error); | ||
} | ||
}; | ||
|
||
app.on(["pull_request.opened", "pull_request.synchronize"], handlePrEvent); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export async function handleKeployWorkflowTrigger(context: { repo: () => { owner: any; repo: any; }; payload: { pull_request: { head: { ref: any; }; number: any; }; }; octokit: { repos: { getContent: (arg0: { owner: any; repo: any; path: string; ref: any; }) => any; }; actions: { createWorkflowDispatch: (arg0: { owner: any; repo: any; workflow_id: string; ref: any; }) => any; }; issues: { createComment: (arg0: any) => any; }; }; }) { | ||
const { owner, repo } = context.repo(); | ||
const { ref } = context.payload.pull_request.head; | ||
|
||
try { | ||
await context.octokit.actions.createWorkflowDispatch({ | ||
owner, repo, workflow_id: 'keploy.yaml', ref | ||
}); | ||
} catch (error : any) { | ||
if (error.status === 404) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 404 error handling is good, but catching only 404s and ignoring other errors can hide issues. Any specific reason for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a debugging decision, let me refactor the error handling |
||
await context.octokit.issues.createComment({ | ||
...context.repo(), | ||
issue_number: context.payload.pull_request.number, | ||
body: '⚠️ Failed to run Keploy Tests' | ||
}); | ||
return; | ||
} | ||
throw error; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Generated by Keploy (2.4.5) | ||
version: api.keploy.io/v1beta1 | ||
kind: Http | ||
name: test-1 | ||
spec: | ||
metadata: {} | ||
req: | ||
method: POST | ||
proto_major: 1 | ||
proto_minor: 1 | ||
url: http://localhost:8080/url | ||
header: | ||
Accept: '*/*' | ||
Content-Length: "33" | ||
Content-Type: application/json | ||
Host: localhost:8080 | ||
User-Agent: curl/7.81.0 | ||
body: |- | ||
{ | ||
"url": "https://google.com" | ||
} | ||
timestamp: 2025-02-22T23:12:25.837014092Z | ||
resp: | ||
status_code: 500 | ||
header: | ||
Content-Length: "929" | ||
Content-Type: application/json; charset=utf-8 | ||
Date: Sat, 22 Feb 2025 23:12:50 GMT | ||
body: '{"error":{"Desc":{"Servers":[{"Addr":"localhost:27017","Arbiters":null,"AverageRTT":0,"AverageRTTSet":false,"Compression":null,"CanonicalAddr":"","ElectionID":"000000000000000000000000","HeartbeatInterval":0,"HelloOK":false,"Hosts":null,"LastError":{"ConnectionID":"","Wrapped":{"Code":0,"Message":"connection(localhost:27017[-63]) socket was unexpectedly closed: EOF","Labels":["NetworkError"],"Name":"","Wrapped":{"ConnectionID":"localhost:27017[-63]","Wrapped":{}},"TopologyVersion":null}},"LastUpdateTime":"0001-01-01T00:00:00Z","LastWriteTime":"0001-01-01T00:00:00Z","MaxBatchCount":0,"MaxDocumentSize":0,"MaxMessageSize":0,"Members":null,"Passives":null,"Passive":false,"Primary":"","ReadOnly":false,"ServiceID":null,"SessionTimeoutMinutes":0,"SetName":"","SetVersion":0,"Tags":null,"TopologyVersion":null,"Kind":0,"WireVersion":null}],"SetName":"","Kind":0,"SessionTimeoutMinutes":0,"CompatibilityErr":null},"Wrapped":{}}}' | ||
status_message: Internal Server Error | ||
proto_major: 0 | ||
proto_minor: 0 | ||
timestamp: 2025-02-22T23:12:52.683242659Z | ||
objects: [] | ||
assertions: | ||
noise: | ||
body.error.Desc.Servers.LastUpdateTime: [] | ||
body.error.Desc.Servers.LastWriteTime: [] | ||
header.Date: [] | ||
created: 1740265972 | ||
curl: |- | ||
curl --request POST \ | ||
--url http://localhost:8080/url \ | ||
--header 'Host: localhost:8080' \ | ||
--header 'User-Agent: curl/7.81.0' \ | ||
--header 'Accept: */*' \ | ||
--header 'Content-Type: application/json' \ | ||
--data "{\n \"url\": \"https://google.com\"\n}" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export async function handleLintWorkflowTrigger(context: { repo: () => { owner: any; repo: any; }; payload: { pull_request: { head: { ref: any; }; number: any; }; }; octokit: { repos: { getContent: (arg0: { owner: any; repo: any; path: string; ref: any; }) => any; }; actions: { createWorkflowDispatch: (arg0: { owner: any; repo: any; workflow_id: string; ref: any; }) => any; }; issues: { createComment: (arg0: any) => any; }; }; }) { | ||
const { owner, repo } = context.repo(); | ||
const { ref } = context.payload.pull_request.head; | ||
|
||
try { | ||
await context.octokit.actions.createWorkflowDispatch({ | ||
owner, repo, workflow_id: 'lint.yaml', ref | ||
}); | ||
} catch (error : any) { | ||
if (error.status === 404) { | ||
await context.octokit.issues.createComment({ | ||
...context.repo(), | ||
issue_number: context.payload.pull_request.number, | ||
body: 'Lint workflow failed' | ||
}); | ||
return; | ||
} | ||
throw error; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add type safety and null checks to avoid runtime errors instead of using any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! Adding those all across the codebase!