|
| 1 | +import * as Plot from "@observablehq/plot"; |
| 2 | +import { useSqlPlot } from "../useSqlPlot"; |
| 3 | +import type { ImportedRepository, TargetSplitgraphRepo } from "../../../types"; |
| 4 | + |
| 5 | +// Assume meta namespace contains both the meta tables, and all imported repositories and tables |
| 6 | +const META_NAMESPACE = |
| 7 | + process.env.NEXT_PUBLIC_SPLITGRAPH_GITHUB_ANALYTICS_META_NAMESPACE; |
| 8 | + |
| 9 | +type CommentLengthRow = { |
| 10 | + username: string; |
| 11 | + comment_length: number; |
| 12 | + net_lines_added: number; |
| 13 | + total_lines_added: number; |
| 14 | + total_lines_deleted: number; |
| 15 | +}; |
| 16 | + |
| 17 | +const sum = (arr: number[]) => arr.reduce((a, b) => a + b, 0); |
| 18 | +const mean = (arr: number[]) => sum(arr) / arr.length; |
| 19 | + |
| 20 | +/** |
| 21 | + * A scatter plot of user comment length vs. lines of code |
| 22 | + */ |
| 23 | +export const UserCodeVsComment = ({ |
| 24 | + splitgraphNamespace, |
| 25 | + splitgraphRepository, |
| 26 | +}: ImportedRepository) => { |
| 27 | + const renderPlot = useSqlPlot({ |
| 28 | + sqlParams: { splitgraphNamespace, splitgraphRepository }, |
| 29 | + buildQuery: userStatsQuery, |
| 30 | + mapRows: (r: UserStatsRow) => |
| 31 | + ({ |
| 32 | + username: r.username, |
| 33 | + comment_length: r.total_comment_length, |
| 34 | + total_lines_added: r.total_lines_added, |
| 35 | + total_lines_deleted: r.total_lines_deleted, |
| 36 | + net_lines_added: r.total_lines_added - r.total_lines_deleted, |
| 37 | + } as CommentLengthRow), |
| 38 | + isRenderable: (p) => !!p.splitgraphRepository, |
| 39 | + reduceRows: (rows: CommentLengthRow[]) => |
| 40 | + rows.filter((r) => r.username && !r.username.endsWith("[bot]")), |
| 41 | + |
| 42 | + makePlotOptions: (userStats: CommentLengthRow[]) => ({ |
| 43 | + y: { |
| 44 | + label: "Length of Comments", |
| 45 | + type: "symlog", |
| 46 | + constant: mean(userStats.map((u) => u.comment_length)), |
| 47 | + }, |
| 48 | + x: { |
| 49 | + label: "Lines of Code", |
| 50 | + type: "symlog", |
| 51 | + constant: mean(userStats.map((u) => u.total_lines_added)), |
| 52 | + }, |
| 53 | + color: { |
| 54 | + scheme: "Turbo", |
| 55 | + }, |
| 56 | + marks: [ |
| 57 | + Plot.dot(userStats, { |
| 58 | + x: "comment_length", |
| 59 | + y: "total_lines_added", |
| 60 | + stroke: "username", |
| 61 | + fill: "username", |
| 62 | + tip: true, |
| 63 | + }), |
| 64 | + Plot.ruleY([0]), |
| 65 | + ], |
| 66 | + }), |
| 67 | + }); |
| 68 | + |
| 69 | + return renderPlot(); |
| 70 | +}; |
| 71 | + |
| 72 | +/** Shape of row returned by {@link userStatsQuery} */ |
| 73 | +export type UserStatsRow = { |
| 74 | + username: string; |
| 75 | + total_commits: number; |
| 76 | + total_pull_request_comments: number; |
| 77 | + total_issue_comments: number; |
| 78 | + total_comment_length: number; |
| 79 | + total_merged_pull_requests: number; |
| 80 | + total_pull_requests: number; |
| 81 | + total_lines_added: number; |
| 82 | + total_lines_deleted: number; |
| 83 | +}; |
| 84 | + |
| 85 | +/** Time series of GitHub stargazers for the given repository */ |
| 86 | +export const userStatsQuery = ({ |
| 87 | + splitgraphNamespace = META_NAMESPACE, |
| 88 | + splitgraphRepository, |
| 89 | +}: TargetSplitgraphRepo) => { |
| 90 | + return `SELECT |
| 91 | + username, |
| 92 | + sum(no_commits) as total_commits, |
| 93 | + sum(no_pull_request_comments) as total_pull_request_comments, |
| 94 | + sum(no_issue_comments) as total_issue_comments, |
| 95 | + sum(total_comment_length) as total_comment_length, |
| 96 | + sum(merged_pull_requests) as total_merged_pull_requests, |
| 97 | + sum(total_pull_requests) as total_pull_requests, |
| 98 | + sum(lines_added) as total_lines_added, |
| 99 | + sum(lines_deleted) as total_lines_deleted |
| 100 | +FROM "${splitgraphNamespace}/${splitgraphRepository}"."monthly_user_stats" |
| 101 | +GROUP BY username;`; |
| 102 | +}; |
0 commit comments