diff --git a/app/discussions/layout.tsx b/app/discussions/layout.tsx index 6c20f33e..c35988a7 100644 --- a/app/discussions/layout.tsx +++ b/app/discussions/layout.tsx @@ -4,7 +4,6 @@ import { notFound } from "next/navigation"; import { featureIsEnabled } from "@/lib/utils"; import FilterDiscussions from "../../components/discussions/FilterDiscussions"; import { categories } from "../../lib/discussion"; -import DiscussionLeaderboard from "../../components/discussions/DiscussionLeaderboard"; import { Suspense } from "react"; export const metadata: Metadata = { @@ -28,7 +27,6 @@ export default function DiscussionsLayout({
{children} -
); diff --git a/app/discussions/page.tsx b/app/discussions/page.tsx index df32f753..1da399ba 100644 --- a/app/discussions/page.tsx +++ b/app/discussions/page.tsx @@ -1,12 +1,97 @@ import { fetchGithubDiscussion } from "../../lib/discussion"; import GithubDiscussions from "../../components/discussions/GithubDiscussions"; +import { getContributors } from "@/lib/api"; +import Link from "next/link"; export default async function Page() { const discussions = await fetchGithubDiscussion(); + if (!discussions) { + return null; + } + + const contributors = await calculateContributor(); + return ( - discussions && ( + <> - ) +
+
+

Most Helpful

+
+ +
+ {contributors + .filter((contributor) => contributor.points > 0) + .sort((a, b) => b.points - a.points) + .slice(0, 3) + .map((contributor, index) => ( + + + + {index + 1} + + + {contributor.name} + + + + ))} +
+
+ + Show More + +
+
+ ); } + +interface Contributor { + name: string; + points: number; + githubHandle: string; +} + +async function calculateContributor() { + const contributors = await getContributors(); + + // If we have contributors then we will calculate the top contributors and save it in an array of objects {name, points, githubHandle} + // points = 1 for each comment + 2 for creating a discussion + 5 discussion marked as helpful + if (contributors) { + const uniqueContributors: Contributor[] = []; + + contributors.forEach((contributor) => { + const existingContributor = uniqueContributors.find( + (c) => c.name === contributor.name, + ); + + const points = + contributor.highlights.discussion_answered || + contributor.highlights.discussion_created || + contributor.highlights.discussion_comment_created || + 0; + + if (existingContributor) { + existingContributor.points += points; + } else { + uniqueContributors.push({ + name: contributor.name, + points: points, + githubHandle: contributor.slug, + }); + } + }); + + return uniqueContributors; + } + + return []; +} diff --git a/app/feed/page.tsx b/app/feed/page.tsx index 8030e88f..34986e9d 100644 --- a/app/feed/page.tsx +++ b/app/feed/page.tsx @@ -7,7 +7,6 @@ import octokit from "@/lib/octokit"; const GITHUB_ORG: string = env.NEXT_PUBLIC_GITHUB_ORG; export default async function FeedPage() { - console.log(GITHUB_ORG); const events = await octokit.paginate( "GET /orgs/{org}/events", { diff --git a/components/Markdown.tsx b/components/Markdown.tsx index b8514600..b1721ace 100644 --- a/components/Markdown.tsx +++ b/components/Markdown.tsx @@ -5,7 +5,7 @@ import remarkRehype from "remark-rehype"; import rehypeStringify from "rehype-stringify"; import clsx from "clsx"; -export default async function Markdown(props: { +export default function Markdown(props: { children: string; className?: string; }) { diff --git a/components/contributors/BadgeIcons.tsx b/components/contributors/BadgeIcons.tsx index 937229ca..a1252d3a 100644 --- a/components/contributors/BadgeIcons.tsx +++ b/components/contributors/BadgeIcons.tsx @@ -6,7 +6,7 @@ import { GraduateAttribute } from "@/config/GraduateAttributes"; import Image from "next/image"; function useOnClickOutside( - ref: RefObject, + ref: RefObject, handler: () => void, ) { useEffect(() => { diff --git a/components/contributors/InfoCard.tsx b/components/contributors/InfoCard.tsx index e60b9cf2..d57b9776 100644 --- a/components/contributors/InfoCard.tsx +++ b/components/contributors/InfoCard.tsx @@ -1,4 +1,3 @@ -/* */ import { Contributor } from "@/lib/types"; import clsx from "clsx"; import Link from "next/link"; @@ -6,7 +5,7 @@ import { BsSlack, BsTwitterX, BsLinkedin, BsGithub } from "react-icons/bs"; import { env } from "@/env.mjs"; import ContributorImage from "./ContributorImage"; -export default async function InfoCard({ +export default function InfoCard({ contributor, rank = null, isClickable = false, diff --git a/components/discussions/DiscussionLeaderboard.tsx b/components/discussions/DiscussionLeaderboard.tsx deleted file mode 100644 index ebbd3f91..00000000 --- a/components/discussions/DiscussionLeaderboard.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { getContributors } from "@/lib/api"; -import Link from "next/link"; - -interface Contributor { - name: string; - points: number; - githubHandle: string; -} - -export async function calculateContributor() { - const contributors = await getContributors(); - - // If we have contributors then we will calculate the top contributors and save it in an array of objects {name, points, githubHandle} - // points = 1 for each comment + 2 for creating a discussion + 5 discussion marked as helpful - if (contributors) { - const uniqueContributors: Contributor[] = []; - - contributors.forEach((contributor) => { - const existingContributor = uniqueContributors.find( - (c) => c.name === contributor.name, - ); - - const points = - contributor.highlights.discussion_answered || - contributor.highlights.discussion_created || - contributor.highlights.discussion_comment_created || - 0; - - if (existingContributor) { - existingContributor.points += points; - } else { - uniqueContributors.push({ - name: contributor.name, - points: points, - githubHandle: contributor.slug, - }); - } - }); - - return uniqueContributors; - } - - return []; -} - -const DiscussionLeaderboard = async () => { - const contributors = await calculateContributor(); - - return ( - <> -
-
-

Most Helpful

-
- -
- {contributors - .filter((contributor) => contributor.points > 0) - .sort((a, b) => b.points - a.points) - .slice(0, 3) - .map((contributor, index) => ( - - - - {index + 1} - - - {contributor.name} - - - - ))} -
-
- - Show More - -
-
- - ); -}; - -export default DiscussionLeaderboard; diff --git a/package.json b/package.json index 945054a3..83ed1dcd 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "node ./scripts/checkData.js && next dev", - "build": "node ./scripts/loadOrgData.js && next build", + "build": "next build", "start": "next start", "lint": "next lint", "lint-fix": "eslint . --fix", @@ -26,9 +26,9 @@ "jotai": "^2.10.2", "next": "^14.2.10", "next-themes": "^0.2.1", - "react": "^18.3.1", + "react": "^19.0.0", "react-activity-calendar": "^2.2.11", - "react-dom": "^18.3.1", + "react-dom": "^19.0.0", "react-icons": "^4.12.0", "rehype-stringify": "^9.0.4", "remark": "^14.0.3", @@ -43,7 +43,7 @@ "devDependencies": { "@tailwindcss/typography": "^0.5.13", "@types/node": "^20.14.12", - "@types/react": "^18.3.3", + "@types/react": "^19.0.1", "autoprefixer": "^10.4.19", "chai": "^5.1.1", "chai-json-schema": "^1.5.1", @@ -63,5 +63,8 @@ "tailwindcss": "^3.4.6", "typescript": "^5.5.4", "yaml": "^2.5.0" + }, + "overrides": { + "@types/react": "19.0.1" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3436367..fd6764e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@headlessui/react': specifier: ^1.7.19 - version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.7.19(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@octokit/core': specifier: ^6.1.2 version: 6.1.2 @@ -37,25 +37,25 @@ importers: version: 4.0.3 jotai: specifier: ^2.10.2 - version: 2.10.2(@types/react@18.3.3)(react@18.3.1) + version: 2.10.2(@types/react@19.0.1)(react@19.0.0) next: specifier: ^14.2.10 - version: 14.2.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@14.2.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.1(next@14.2.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-activity-calendar: specifier: ^2.2.11 - version: 2.2.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.2.11(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) react-icons: specifier: ^4.12.0 - version: 4.12.0(react@18.3.1) + version: 4.12.0(react@19.0.0) rehype-stringify: specifier: ^9.0.4 version: 9.0.4 @@ -79,7 +79,7 @@ importers: version: 10.1.2 use-debounce: specifier: ^10.0.1 - version: 10.0.1(react@18.3.1) + version: 10.0.1(react@19.0.0) zod: specifier: ^3.23.8 version: 3.23.8 @@ -91,8 +91,8 @@ importers: specifier: ^20.14.12 version: 20.14.12 '@types/react': - specifier: ^18.3.3 - version: 18.3.3 + specifier: ^19.0.1 + version: 19.0.1 autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.40) @@ -518,11 +518,8 @@ packages: '@types/prismjs@1.26.4': resolution: {integrity: sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==} - '@types/prop-types@15.7.12': - resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - - '@types/react@18.3.3': - resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/react@19.0.1': + resolution: {integrity: sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==} '@types/unist@2.0.10': resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} @@ -820,8 +817,8 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} crypto-js@4.2.0: @@ -1113,6 +1110,7 @@ packages: eslint@8.16.0: resolution: {integrity: sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -1785,8 +1783,8 @@ packages: micromark@3.2.0: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} minimatch@3.1.2: @@ -2129,10 +2127,10 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: - react: ^18.3.1 + react: ^19.0.0 react-icons@4.12.0: resolution: {integrity: sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==} @@ -2142,8 +2140,8 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -2232,8 +2230,8 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} section-matter@1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} @@ -2632,12 +2630,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@headlessui/react@1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@headlessui/react@1.7.19(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@tanstack/react-virtual': 3.8.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-virtual': 3.8.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) client-only: 0.0.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) '@humanwhocodes/config-array@0.9.5': dependencies: @@ -2967,11 +2965,11 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.4.6 - '@tanstack/react-virtual@3.8.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tanstack/react-virtual@3.8.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@tanstack/virtual-core': 3.8.3 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) '@tanstack/virtual-core@3.8.3': {} @@ -3009,11 +3007,8 @@ snapshots: '@types/prismjs@1.26.4': {} - '@types/prop-types@15.7.12': {} - - '@types/react@18.3.3': + '@types/react@19.0.1': dependencies: - '@types/prop-types': 15.7.12 csstype: 3.1.3 '@types/unist@2.0.10': {} @@ -3333,7 +3328,7 @@ snapshots: concat-map@0.0.1: {} - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -3583,7 +3578,7 @@ snapshots: eslint: 8.16.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.16.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.16.0) eslint-plugin-react: 7.35.0(eslint@8.16.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.16.0) @@ -3610,8 +3605,8 @@ snapshots: debug: 4.3.5(supports-color@8.1.1) enhanced-resolve: 5.17.1 eslint: 8.16.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.16.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.16.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 is-core-module: 2.15.0 @@ -3622,7 +3617,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.16.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -3633,7 +3628,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.16.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -3643,7 +3638,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.16.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.16.0))(eslint@8.16.0))(eslint@8.16.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.16.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.16.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -3741,7 +3736,7 @@ snapshots: '@humanwhocodes/config-array': 0.9.5 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.3.5(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 @@ -3811,7 +3806,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-json-stable-stringify@2.1.0: {} @@ -3850,7 +3845,7 @@ snapshots: foreground-child@3.2.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 fraction.js@4.3.7: {} @@ -4216,10 +4211,10 @@ snapshots: jiti@1.21.6: {} - jotai@2.10.2(@types/react@18.3.3)(react@18.3.1): + jotai@2.10.2(@types/react@19.0.1)(react@19.0.0): optionalDependencies: - '@types/react': 18.3.3 - react: 18.3.1 + '@types/react': 19.0.1 + react: 19.0.0 js-tokens@4.0.0: {} @@ -4639,7 +4634,7 @@ snapshots: transitivePeerDependencies: - supports-color - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -4703,13 +4698,13 @@ snapshots: natural-compare@1.4.0: {} - next-themes@0.2.1(next@14.2.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - next: 14.2.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + next: 14.2.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - next@14.2.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@next/env': 14.2.10 '@swc/helpers': 0.5.5 @@ -4717,9 +4712,9 @@ snapshots: caniuse-lite: 1.0.30001643 graceful-fs: 4.2.11 postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.1(react@19.0.0) optionalDependencies: '@next/swc-darwin-arm64': 14.2.10 '@next/swc-darwin-x64': 14.2.10 @@ -4914,11 +4909,11 @@ snapshots: prettier@3.3.3: {} - prism-react-renderer@2.3.1(react@18.3.1): + prism-react-renderer@2.3.1(react@19.0.0): dependencies: '@types/prismjs': 1.26.4 clsx: 2.1.1 - react: 18.3.1 + react: 19.0.0 prop-types@15.8.1: dependencies: @@ -4936,30 +4931,27 @@ snapshots: dependencies: safe-buffer: 5.2.1 - react-activity-calendar@2.2.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-activity-calendar@2.2.11(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@types/chroma-js': 2.4.4 chroma-js: 2.4.2 date-fns: 3.6.0 - prism-react-renderer: 2.3.1(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + prism-react-renderer: 2.3.1(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - react-dom@18.3.1(react@18.3.1): + react-dom@19.0.0(react@19.0.0): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.0.0 + scheduler: 0.25.0 - react-icons@4.12.0(react@18.3.1): + react-icons@4.12.0(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 react-is@16.13.1: {} - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + react@19.0.0: {} read-cache@1.0.0: dependencies: @@ -5082,9 +5074,7 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.25.0: {} section-matter@1.0.0: dependencies: @@ -5223,10 +5213,10 @@ snapshots: strip-json-comments@5.0.1: {} - styled-jsx@5.1.1(react@18.3.1): + styled-jsx@5.1.1(react@19.0.0): dependencies: client-only: 0.0.1 - react: 18.3.1 + react: 19.0.0 sucrase@3.35.0: dependencies: @@ -5265,7 +5255,7 @@ snapshots: is-glob: 4.0.3 jiti: 1.21.6 lilconfig: 2.1.0 - micromatch: 4.0.7 + micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.1 @@ -5420,9 +5410,9 @@ snapshots: dependencies: punycode: 2.3.1 - use-debounce@10.0.1(react@18.3.1): + use-debounce@10.0.1(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 util-deprecate@1.0.2: {} diff --git a/scraper/src/github-scraper/parseEvents.ts b/scraper/src/github-scraper/parseEvents.ts index c128cfb5..be54222a 100644 --- a/scraper/src/github-scraper/parseEvents.ts +++ b/scraper/src/github-scraper/parseEvents.ts @@ -191,7 +191,16 @@ export const parseEvents = async (events: IGitHubEvent[]) => { event.payload.action === "closed" && event.payload.pull_request?.merged ) { - const turnaroundTime = await calculateTurnaroundTime(event); + let turnaroundTime: number | undefined = undefined; + try { + turnaroundTime = await calculateTurnaroundTime(event); + } catch (e) { + console.error( + `Error calculating turnaround time for event ${event.id}: ${e}`, + `Likely due to PR author ${event.payload.pull_request.user.login} being deleted`, + event, + ); + } appendEvent(event.payload.pull_request.user.login, { type: "pr_merged", title: `${event.repo.name}#${event.payload.pull_request.number}`, @@ -200,7 +209,14 @@ export const parseEvents = async (events: IGitHubEvent[]) => { text: event.payload.pull_request.title, turnaround_time: turnaroundTime, }); - await addCollaborations(event, eventTime); + try { + await addCollaborations(event, eventTime); + } catch (e) { + console.error( + `Error adding collaborations for event ${event.id}: ${e}`, + event, + ); + } } break; case "PullRequestReviewEvent": diff --git a/scraper/src/github-scraper/projectItems.ts b/scraper/src/github-scraper/projectItems.ts index a43f05d2..11cc38f7 100644 --- a/scraper/src/github-scraper/projectItems.ts +++ b/scraper/src/github-scraper/projectItems.ts @@ -11,13 +11,36 @@ import { mkdir, readFile, writeFile } from "fs/promises"; import { octokit } from "./config.js"; import path from "path"; +interface ProjectBoardItem { + user: string; + type: "PULL_REQUEST" | "ISSUE" | "DRAFT_ISSUE"; + url: string; + title: string; + createdAt: string; + updatedAt: string; + closedAt: string | null; + completedAtMonth: string | null; + sprint: string | null; + category: string | null; + status: string | null; + storyPoints: number | null; + priority: string | null; + author: string; + assignees: string; // comma separated list of assignees + focus: string | null; +} + async function getProjectBoardItems(projectId: string) { - const data: any = await octokit.graphql( - `query getProjectItems($projectId: ID!) { + const iterator = octokit.graphql.paginate.iterator( + `query getProjectItems($projectId: ID!, $cursor: String) { node(id: $projectId) { ... on ProjectV2 { updatedAt - items(first: 100, orderBy: {field: POSITION, direction: DESC}) { + items(first: 100, orderBy: {field: POSITION, direction: DESC}, after: $cursor) { + pageInfo { + hasNextPage + endCursor + } nodes { id createdAt @@ -74,6 +97,7 @@ async function getProjectBoardItems(projectId: string) { ... on Issue { url closedAt + stateReason author { login } @@ -86,6 +110,8 @@ async function getProjectBoardItems(projectId: string) { ... on PullRequest { url closedAt + merged + mergedAt author { login } @@ -104,37 +130,74 @@ async function getProjectBoardItems(projectId: string) { { projectId }, ); - return Object.fromEntries( - data.node.items.nodes.map((node: any) => { + let items: [string, ProjectBoardItem][] = []; + + for await (const response of iterator) { + console.log(`Processing ${response.node.items.nodes.length} items`); + + for (const node of response.node.items.nodes) { const get = (fieldName: string) => { return node.fieldValues.nodes.find( (fieldValue: any) => fieldValue.field?.name === fieldName, ); }; - return [ - node.id, - { - type: node.type, - url: node.content.url, - title: get("Title").text, - createdAt: node.createdAt, - updatedAt: node.updatedAt, - closedAt: node.content.closedAt, - sprint: get("Sprint")?.title, - category: get("Category")?.name, - status: get("Status")?.name, - storyPoints: get("Story Points")?.number, - priority: get("Priority")?.name, - author: node.content.author?.login, - assignees: node.content.assignees.nodes.map( - (user: any) => user.login, - ), - focus: get("Focus")?.name, - }, - ]; - }), - ); + let assignees = new Set( + node.content.assignees.nodes.map((user: any) => user.login), + ); + + // Adding author as assignee for PRs because they may not be present in + // the assignees list. + if (node.content.type === "PULL_REQUEST") { + const author = node.content.author?.login; + if (author) { + assignees.add(author); + } + } + + let completedAt = null; + + if (node.type === "ISSUE" && node.content.stateReason === "COMPLETED") { + completedAt = node.content.closedAt; + } + if (node.type === "PULL_REQUEST" && node.content.merged) { + completedAt = node.content.mergedAt; + } + + const baseData = { + type: node.type, + url: node.content.url, + title: get("Title").text, + createdAt: node.createdAt, + updatedAt: node.updatedAt, + closedAt: node.content.closedAt, + completedAtMonth: + completedAt && + new Date(completedAt).toLocaleString("default", { + month: "short", + year: "numeric", + }), + sprint: get("Sprint")?.title, + assignees: Array.from(assignees).join(","), + category: get("Category")?.name, + status: get("Status")?.name, + storyPoints: get("Story Points")?.number, + priority: get("Priority")?.name, + author: node.content.author?.login, + focus: get("Focus")?.name, + } satisfies Omit; + + // Voluntarily duplicating for each contributor because, contributors + // are the first class citizen in the cache. + assignees.forEach((assignee) => { + items.push([`${node.id}/${assignee}`, { ...baseData, user: assignee }]); + }); + } + } + + console.log(`Processed ${items.length} items`); + + return Object.fromEntries(items); } async function readExistingItems(filePath: string): Promise { diff --git a/scripts/loadOrgData.js b/scripts/loadOrgData.js index a2cfea13..aa4a90ea 100644 --- a/scripts/loadOrgData.js +++ b/scripts/loadOrgData.js @@ -1,8 +1,13 @@ const { execSync } = require("child_process"); const fs = require("fs"); const path = require("path"); -const dotenv = require("dotenv"); -dotenv.config(); + +try { + const dotenv = require("dotenv"); + dotenv.config(); +} catch (error) { + console.error("No .env file found"); +} const DATA_SOURCE = process.env.DATA_SOURCE || null; if (!DATA_SOURCE) { diff --git a/vercel.json b/vercel.json deleted file mode 100644 index f7bffd13..00000000 --- a/vercel.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "framework": "nextjs", - "buildCommand": "node ./scripts/loadOrgData.js && next build" -}