Open Source Team Metrics based on PRs

github fixes

Changed files
+132 -8
app
lib
+17
app/icon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> 2 + <style> 3 + path { 4 + fill: hsl(var(--primary)); /* Default to primary theme color */ 5 + } 6 + @media (prefers-color-scheme: dark) { 7 + path { 8 + fill: #FFFFFF; /* White for dark mode */ 9 + } 10 + } 11 + </style> 12 + <path 13 + fillRule="evenodd" 14 + d="M1.5 9c0 0.69781 0.10996 1.3699 0.3135 2H0v1.5h2.52182c0.11848 0.1851 0.24608 0.3637 0.38217 0.5353L1 14.9393 2.06066 16l1.90399 -1.904C5.07323 14.975 6.47531 15.5 8 15.5s2.9268 -0.525 4.0353 -1.404L13.9393 16 15 14.9393l-1.904 -1.904c0.1361 -0.1716 0.2637 -0.3502 0.3822 -0.5353H16V11h-1.8135c0.2035 -0.6301 0.3135 -1.30219 0.3135 -2V0.5C12.2612 0.5 10.366 1.97145 9.7289 4H6.2711C5.63397 1.97145 3.73882 0.5 1.5 0.5V9ZM8 13l-2 -2h4l-2 2Z" 15 + clipRule="evenodd" 16 + /> 17 + </svg>
+59 -5
lib/repositories/index.ts
··· 1 1 // Import and re-export all repositories for easier imports 2 2 3 - export * from './user-repository'; 4 - export * from './organization-repository'; 5 - export * from './repository-repository'; 6 - export * from './category-repository'; 7 - export * from './pr-repository'; 3 + export { 4 + findUserById, 5 + findUserByEmail, 6 + createUser, 7 + updateUser, 8 + updateOrganizationRole, 9 + getUserOrganizations, 10 + addUserToOrganization, 11 + removeUserFromOrganization, 12 + getOrganizationRole, 13 + findOrCreateUserByGitHubId, 14 + } from './user-repository'; 15 + export { 16 + findOrCreateOrganization, 17 + findOrganizationById, 18 + updateOrganization, 19 + } from './organization-repository'; 20 + export { 21 + findRepositoryById, 22 + findRepositoryByGitHubId, 23 + findOrCreateRepository, 24 + updateRepository, 25 + getOrganizationRepositories, 26 + setRepositoryTracking, 27 + getTrackedRepositories, 28 + findRepositoryByFullName, 29 + } from './repository-repository'; 30 + export { 31 + createPullRequest, 32 + findPullRequestById, 33 + findPullRequestByNumber, 34 + updatePullRequest, 35 + getRepositoryPullRequests, 36 + createPullRequestReview, 37 + findReviewByGitHubId, 38 + updatePullRequestReview, 39 + } from './pr-repository'; 40 + export { 41 + getDefaultCategories, 42 + getOrganizationCategories, 43 + createCategory, 44 + updateCategory, 45 + deleteCategory, 46 + findCategoryById 47 + } from './category-repository'; 48 + // Commented out sections for missing files remain for user to address 49 + // export { 50 + // getSettings, 51 + // updateSetting, 52 + // getOrganizationSettings, 53 + // updateOrganizationSetting 54 + // } from './settings-repository'; 55 + // export { 56 + // createRecommendation, 57 + // getRecommendationsByOrganizationId, 58 + // updateRecommendationStatus 59 + // } from './recommendation-repository'; 60 + 61 + // export * from './schema-version-repository';
+26
lib/repositories/user-repository.ts
··· 127 127 'UPDATE user_organizations SET role = ? WHERE user_id = ? AND organization_id = ?', 128 128 [role, userId, organizationId] 129 129 ); 130 + } 131 + 132 + export async function findOrCreateUserByGitHubId(userData: { 133 + id: string; // GitHub user ID 134 + login: string; // GitHub login/username 135 + email?: string | null; 136 + avatar_url?: string | null; 137 + name?: string | null; // GitHub display name 138 + }): Promise<User> { 139 + const existingUser = await findUserById(userData.id); 140 + if (existingUser) { 141 + // Optionally update user details if they've changed 142 + // For now, just return the existing user 143 + return existingUser; 144 + } 145 + 146 + // User not found, create a new one 147 + // The users table expects 'name', 'email', 'image' 148 + // We'll use github login for name if display name is not available 149 + // and github avatar_url for image 150 + return createUser({ 151 + id: userData.id, 152 + name: userData.name || userData.login, 153 + email: userData.email || null, 154 + image: userData.avatar_url || null, 155 + }); 130 156 }
+30 -3
lib/services/github-service.ts
··· 11 11 setRepositoryTracking, 12 12 findRepositoryById, 13 13 findRepositoryByGitHubId, 14 - addUserToOrganization 14 + addUserToOrganization, 15 + findOrCreateUserByGitHubId 15 16 } from '@/lib/repositories'; 16 17 import { GitHubRepository, GitHubPullRequest, GitHubOrganization, GitHubUser, PRReview } from '@/lib/types'; 17 18 ··· 101 102 ? 'merged' 102 103 : pr.state === 'closed' ? 'closed' : 'open'; 103 104 105 + // Ensure author exists in our database 106 + const prAuthor = pr.user ? await findOrCreateUserByGitHubId({ 107 + id: pr.user.id.toString(), 108 + login: pr.user.login, 109 + avatar_url: pr.user.avatar_url, 110 + name: pr.user.name // pr.user might not have 'name', adjust if necessary based on GitHub API response 111 + }) : null; 112 + 104 113 if (existingPR) { 105 114 // Update existing PR 106 115 await updatePullRequest(existingPR.id, { ··· 111 120 closed_at: pr.closed_at, 112 121 merged_at: pr.merged_at, 113 122 draft: pr.draft, 123 + // author_id is not typically updated, but if it could change or be initially null, handle here 114 124 }); 115 125 } else { 116 126 // Create new PR 127 + if (!prAuthor) { 128 + console.warn(`Skipping PR #${pr.number} for repo ${owner}/${repo} due to missing author information from GitHub.`); 129 + return; // Skip this PR if author couldn't be processed 130 + } 117 131 const newPR = await createPullRequest({ 118 132 github_id: pr.id, 119 133 repository_id: repositoryId, 120 134 number: pr.number, 121 135 title: pr.title, 122 136 description: pr.body || null, 123 - author_id: pr.user.id.toString(), // This might require creating user records 137 + author_id: prAuthor.id, // Use the ID from our users table 124 138 state, 125 139 created_at: pr.created_at, 126 140 updated_at: pr.updated_at, ··· 154 168 const existingReview = await findReviewByGitHubId(review.id); 155 169 156 170 if (!existingReview) { 171 + // Ensure reviewer exists in our database 172 + const reviewAuthor = await findOrCreateUserByGitHubId({ 173 + id: review.user.id.toString(), 174 + login: review.user.login, 175 + avatar_url: review.user.avatar_url, 176 + name: review.user.name // review.user might not have 'name', adjust if necessary 177 + }); 178 + 179 + if (!reviewAuthor) { 180 + console.warn(`Skipping review for PR #${prNumber} in ${owner}/${repo} by ${review.user.login} due to missing author information.`); 181 + return; // Skip this review if author couldn't be processed 182 + } 183 + 157 184 // Map GitHub review state to our enum 158 185 const reviewState = this.mapReviewState(review.state); 159 186 160 187 await createPullRequestReview({ 161 188 github_id: review.id, 162 189 pull_request_id: pullRequestId, 163 - reviewer_id: review.user.id.toString(), // This might require creating user records 190 + reviewer_id: reviewAuthor.id, // Use the ID from our users table 164 191 state: reviewState, 165 192 submitted_at: review.submitted_at 166 193 });