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