forked from wishonia/wishonia
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduced functions to generate and save GitHub issues from meeting transcripts. Added unit tests to verify the correct creation and saving of issues.
- Loading branch information
Showing
2 changed files
with
172 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { z } from 'zod'; | ||
import { generateObject } from 'ai'; | ||
import { Octokit } from '@octokit/rest'; | ||
import { anthropic } from "@ai-sdk/anthropic"; | ||
import { LanguageModelV1 } from "@ai-sdk/provider"; | ||
|
||
const GeneratedIssueSchema = z.object({ | ||
title: z.string().describe('The title of the GitHub issue'), | ||
body: z.string().describe('The body content of the GitHub issue'), | ||
labels: z.array(z.string()).describe('Labels for the issue'), | ||
assignees: z.array(z.string()).describe('GitHub usernames of assignees'), | ||
}); | ||
|
||
const GeneratedIssuesWrapperSchema = z.object({ | ||
issues: z.array(GeneratedIssueSchema).describe('An array of generated GitHub issues'), | ||
}); | ||
|
||
type GeneratedIssue = z.infer<typeof GeneratedIssueSchema>; | ||
|
||
export async function createIssuesFromTranscript( | ||
transcript: string, | ||
repoOwner: string, | ||
repoName: string, | ||
options: { | ||
modelName?: 'claude-3-sonnet-20240229' | 'claude-3-opus-20240229', | ||
maxIssues?: number, | ||
} = {} | ||
): Promise<GeneratedIssue[]> { | ||
const { | ||
modelName = 'claude-3-sonnet-20240229', | ||
maxIssues = 5, | ||
} = options; | ||
|
||
console.log(`Analyzing transcript for repo: ${repoOwner}/${repoName}`); | ||
|
||
const model: LanguageModelV1 = anthropic(modelName); | ||
|
||
const prompt = ` | ||
Analyze the following meeting transcript and create GitHub issues based on the discussion. | ||
Focus on action items, tasks, and important decisions that need to be tracked. | ||
# Guidelines | ||
- Create up to ${maxIssues} issues. | ||
- Each issue should have a clear, concise title. | ||
- The body should provide context and any relevant details from the transcript. | ||
- Assign appropriate labels (e.g., "bug", "feature", "documentation"). | ||
- Suggest assignees based on names mentioned in the transcript. | ||
# Meeting Transcript | ||
${transcript} | ||
`; | ||
|
||
const result = await generateObject({ | ||
model: model, | ||
schema: GeneratedIssuesWrapperSchema, | ||
prompt, | ||
}); | ||
|
||
const generatedIssues = result.object.issues as GeneratedIssue[]; | ||
console.log(`Generated ${generatedIssues.length} issues`); | ||
|
||
return generatedIssues; | ||
} | ||
|
||
export async function saveIssuesToGitHub( | ||
issues: GeneratedIssue[], | ||
repoOwner: string, | ||
repoName: string, | ||
githubToken: string | ||
): Promise<void> { | ||
const octokit = new Octokit({ auth: githubToken }); | ||
|
||
for (const issue of issues) { | ||
try { | ||
const response = await octokit.issues.create({ | ||
owner: repoOwner, | ||
repo: repoName, | ||
title: issue.title, | ||
body: issue.body, | ||
labels: issue.labels, | ||
assignees: issue.assignees, | ||
}); | ||
|
||
console.log(`Created issue: ${response.data.html_url}`); | ||
} catch (error) { | ||
console.error(`Failed to create issue: ${issue.title}`, error); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* @jest-environment node | ||
*/ | ||
import { createIssuesFromTranscript, saveIssuesToGitHub } from "@/lib/github/issueManager"; | ||
import { PrismaClient } from '@prisma/client'; | ||
import { Octokit } from '@octokit/rest'; | ||
|
||
const prisma = new PrismaClient(); | ||
|
||
// Mock Octokit | ||
jest.mock('@octokit/rest'); | ||
|
||
describe("IssueManager tests", () => { | ||
jest.setTimeout(60000); // 60 seconds timeout | ||
|
||
const mockTranscript = ` | ||
Alice: We need to fix the login bug on the homepage. | ||
Bob: Agreed. I'll take that on. We should also add a new feature for user profiles. | ||
Charlie: Good idea. I can work on the documentation for the new API. | ||
Alice: Great. Let's also schedule a performance review next week. | ||
`; | ||
|
||
const repoOwner = 'test-owner'; | ||
const repoName = 'test-repo'; | ||
const githubToken = 'test-token'; | ||
|
||
it("Generates issues from transcript", async () => { | ||
const generatedIssues = await createIssuesFromTranscript(mockTranscript, repoOwner, repoName); | ||
|
||
expect(generatedIssues).not.toBeNull(); | ||
expect(Array.isArray(generatedIssues)).toBe(true); | ||
expect(generatedIssues.length).toBeGreaterThan(0); | ||
|
||
generatedIssues.forEach(issue => { | ||
expect(issue).toHaveProperty('title'); | ||
expect(issue).toHaveProperty('body'); | ||
expect(issue).toHaveProperty('labels'); | ||
expect(issue).toHaveProperty('assignees'); | ||
}); | ||
}); | ||
|
||
it("Saves issues to GitHub", async () => { | ||
const mockIssues = [ | ||
{ | ||
title: 'Fix login bug', | ||
body: 'There is a login bug on the homepage that needs to be fixed.', | ||
labels: ['bug'], | ||
assignees: ['Bob'] | ||
}, | ||
{ | ||
title: 'Add user profiles feature', | ||
body: 'Implement a new feature for user profiles.', | ||
labels: ['feature'], | ||
assignees: ['Bob'] | ||
} | ||
]; | ||
|
||
// Mock the Octokit issues.create method | ||
const mockCreate = jest.fn().mockResolvedValue({ data: { html_url: 'https://github.com/test-owner/test-repo/issues/1' } }); | ||
(Octokit as jest.Mock).mockImplementation(() => ({ | ||
issues: { create: mockCreate } | ||
})); | ||
|
||
await saveIssuesToGitHub(mockIssues, repoOwner, repoName, githubToken); | ||
|
||
expect(mockCreate).toHaveBeenCalledTimes(mockIssues.length); | ||
mockIssues.forEach((issue, index) => { | ||
expect(mockCreate).toHaveBeenNthCalledWith(index + 1, { | ||
owner: repoOwner, | ||
repo: repoName, | ||
title: issue.title, | ||
body: issue.body, | ||
labels: issue.labels, | ||
assignees: issue.assignees | ||
}); | ||
}); | ||
}); | ||
|
||
// Add this afterAll hook to close the Prisma connection | ||
afterAll(async () => { | ||
await prisma.$disconnect(); | ||
}); | ||
}); |