Skip to content

Commit

Permalink
Add GitHub issue automation logic
Browse files Browse the repository at this point in the history
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
mikepsinn committed Sep 8, 2024
1 parent a230104 commit 121bdaf
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 0 deletions.
89 changes: 89 additions & 0 deletions lib/github/issueManager.ts
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);
}
}
}
83 changes: 83 additions & 0 deletions tests/github-issue-agent.test.ts
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();
});
});

0 comments on commit 121bdaf

Please sign in to comment.