From cb69a6b5f2148462163f0f9c32e961dc53c30ee1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 06:41:23 +0000 Subject: [PATCH 1/5] perf: Avoid unnecessary O(N) spread allocation in mergeTopRepository Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/lib/githubYearInReview.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/lib/githubYearInReview.ts b/src/lib/githubYearInReview.ts index 23c4538..a5893f1 100644 --- a/src/lib/githubYearInReview.ts +++ b/src/lib/githubYearInReview.ts @@ -118,16 +118,18 @@ async function graphql(query: string, token: string, variables: Record["contributionsCollection"]): { name: string; contributions: number } | null { const counter = new Map(); - const buckets = [ - ...(data.commitContributionsByRepository || []), - ...(data.pullRequestContributionsByRepository || []), - ...(data.issueContributionsByRepository || []), - ]; - - for (const item of buckets) { - const name = `${item.repository.owner.login}/${item.repository.name}`; - counter.set(name, (counter.get(name) ?? 0) + item.contributions.totalCount); - } + + const countContributions = (bucket: typeof data.commitContributionsByRepository | undefined) => { + if (!bucket) return; + for (const item of bucket) { + const name = `${item.repository.owner.login}/${item.repository.name}`; + counter.set(name, (counter.get(name) ?? 0) + item.contributions.totalCount); + } + }; + + countContributions(data.commitContributionsByRepository); + countContributions(data.pullRequestContributionsByRepository); + countContributions(data.issueContributionsByRepository); let top: { name: string; contributions: number } | null = null; for (const [name, contributions] of counter.entries()) { From 58060aec53913f3373f99a6e9180ff700f51c0ad Mon Sep 17 00:00:00 2001 From: is0692vs Date: Mon, 13 Jul 2026 11:07:29 +0900 Subject: [PATCH 2/5] refactor: simplify top repository aggregation --- src/lib/githubYearInReview.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lib/githubYearInReview.ts b/src/lib/githubYearInReview.ts index a5893f1..36fc68f 100644 --- a/src/lib/githubYearInReview.ts +++ b/src/lib/githubYearInReview.ts @@ -119,17 +119,19 @@ async function graphql(query: string, token: string, variables: Record["contributionsCollection"]): { name: string; contributions: number } | null { const counter = new Map(); - const countContributions = (bucket: typeof data.commitContributionsByRepository | undefined) => { - if (!bucket) return; + const buckets = [ + data.commitContributionsByRepository, + data.pullRequestContributionsByRepository, + data.issueContributionsByRepository, + ]; + + for (const bucket of buckets) { + if (!bucket) continue; for (const item of bucket) { const name = `${item.repository.owner.login}/${item.repository.name}`; counter.set(name, (counter.get(name) ?? 0) + item.contributions.totalCount); } - }; - - countContributions(data.commitContributionsByRepository); - countContributions(data.pullRequestContributionsByRepository); - countContributions(data.issueContributionsByRepository); + } let top: { name: string; contributions: number } | null = null; for (const [name, contributions] of counter.entries()) { From 060cd4f37b0252e08b3bf01cb2b90f34839c32e0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 02:12:36 +0000 Subject: [PATCH 3/5] perf: Avoid unnecessary O(N) spread allocation in mergeTopRepository Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/lib/github.ts | 21 ++++++++++++++++----- src/lib/githubYearInReview.ts | 16 +++++++--------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/lib/github.ts b/src/lib/github.ts index d35c21b..ed7dd37 100644 --- a/src/lib/github.ts +++ b/src/lib/github.ts @@ -743,13 +743,24 @@ export const fetchActivity = cache(async function fetchActivity( /** * 効率的に Map から上位 K 件を抽出するヘルパー関数 - * 上位 K 件だけを返します。 + * 配列の作成とソートを最小限に抑えることでパフォーマンスを向上させます */ function getTopK(map: Map, k: number = 10): { name: string; count: number }[] { - const limit = Math.max(0, k); - return Array.from(map, ([name, count]) => ({ name, count })) - .sort((a, b) => b.count - a.count) - .slice(0, limit); + const top: { name: string; count: number }[] = []; + for (const [name, count] of map.entries()) { + if (top.length < k) { + top.push({ name, count }); + top.sort((a, b) => b.count - a.count); + } else if (count > top[k - 1].count) { + let i = k - 2; + while (i >= 0 && top[i].count < count) { + top[i + 1] = top[i]; + i--; + } + top[i + 1] = { name, count }; + } + } + return top; } /** diff --git a/src/lib/githubYearInReview.ts b/src/lib/githubYearInReview.ts index 36fc68f..a5893f1 100644 --- a/src/lib/githubYearInReview.ts +++ b/src/lib/githubYearInReview.ts @@ -119,19 +119,17 @@ async function graphql(query: string, token: string, variables: Record["contributionsCollection"]): { name: string; contributions: number } | null { const counter = new Map(); - const buckets = [ - data.commitContributionsByRepository, - data.pullRequestContributionsByRepository, - data.issueContributionsByRepository, - ]; - - for (const bucket of buckets) { - if (!bucket) continue; + const countContributions = (bucket: typeof data.commitContributionsByRepository | undefined) => { + if (!bucket) return; for (const item of bucket) { const name = `${item.repository.owner.login}/${item.repository.name}`; counter.set(name, (counter.get(name) ?? 0) + item.contributions.totalCount); } - } + }; + + countContributions(data.commitContributionsByRepository); + countContributions(data.pullRequestContributionsByRepository); + countContributions(data.issueContributionsByRepository); let top: { name: string; contributions: number } | null = null; for (const [name, contributions] of counter.entries()) { From 69785981a2d150f3f87c955e734138dd1501b313 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 02:37:51 +0000 Subject: [PATCH 4/5] perf: Avoid unnecessary O(N) spread allocation in mergeTopRepository Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- src/lib/__tests__/githubYearInReview.test.ts | 81 ++++++++++++++++++++ src/lib/githubYearInReview.ts | 16 ++-- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/lib/__tests__/githubYearInReview.test.ts b/src/lib/__tests__/githubYearInReview.test.ts index f89b1ac..55216c5 100644 --- a/src/lib/__tests__/githubYearInReview.test.ts +++ b/src/lib/__tests__/githubYearInReview.test.ts @@ -408,6 +408,87 @@ describe("fetchYearInReviewData additional coverage", () => { } }); + + + + it("handles missing repository data in mergeTopRepository", async () => { + mockFetch.mockImplementation(() => { + return Promise.resolve(jsonResponse({ + data: { + user: { + id: "123", + contributionsCollection: { + totalCommitContributions: 100, + totalPullRequestContributions: 50, + totalIssueContributions: 10, + totalPullRequestReviewContributions: 0, + contributionCalendar: { totalContributions: 160, weeks: [] }, + } + } + } + })); + }); + + const data = await fetchYearInReviewData("testuser", 2023, "token"); + expect(data.topRepository).toBeNull(); + }); + + it("handles undefined repository data in mergeTopRepository", async () => { + mockFetch.mockImplementation(() => { + return Promise.resolve(jsonResponse({ + data: { + user: { + id: "123", + contributionsCollection: { + totalCommitContributions: 100, + totalPullRequestContributions: 50, + totalIssueContributions: 10, + totalPullRequestReviewContributions: 0, + contributionCalendar: { totalContributions: 160, weeks: [] }, + commitContributionsByRepository: undefined, + pullRequestContributionsByRepository: undefined, + issueContributionsByRepository: undefined, + } + } + } + })); + }); + + const data = await fetchYearInReviewData("testuser", 2023, "token"); + expect(data.topRepository).toBeNull(); + }); + + it("handles full repository data in mergeTopRepository", async () => { + mockFetch.mockImplementation(() => { + return Promise.resolve(jsonResponse({ + data: { + user: { + id: "123", + contributionsCollection: { + totalCommitContributions: 100, + totalPullRequestContributions: 50, + totalIssueContributions: 10, + totalPullRequestReviewContributions: 0, + contributionCalendar: { totalContributions: 160, weeks: [] }, + commitContributionsByRepository: [ + { repository: { owner: { login: "user1" }, name: "repo1" }, contributions: { totalCount: 50 } } + ], + pullRequestContributionsByRepository: [ + { repository: { owner: { login: "user1" }, name: "repo1" }, contributions: { totalCount: 20 } } + ], + issueContributionsByRepository: [ + { repository: { owner: { login: "user1" }, name: "repo1" }, contributions: { totalCount: 10 } } + ], + } + } + } + })); + }); + + const data = await fetchYearInReviewData("testuser", 2023, "token"); + expect(data.topRepository).toEqual({ name: "user1/repo1", contributions: 80 }); + }); + it("handles partial repository data in mergeTopRepository", async () => { mockFetch.mockImplementation(() => { return Promise.resolve(jsonResponse({ diff --git a/src/lib/githubYearInReview.ts b/src/lib/githubYearInReview.ts index a5893f1..36fc68f 100644 --- a/src/lib/githubYearInReview.ts +++ b/src/lib/githubYearInReview.ts @@ -119,17 +119,19 @@ async function graphql(query: string, token: string, variables: Record["contributionsCollection"]): { name: string; contributions: number } | null { const counter = new Map(); - const countContributions = (bucket: typeof data.commitContributionsByRepository | undefined) => { - if (!bucket) return; + const buckets = [ + data.commitContributionsByRepository, + data.pullRequestContributionsByRepository, + data.issueContributionsByRepository, + ]; + + for (const bucket of buckets) { + if (!bucket) continue; for (const item of bucket) { const name = `${item.repository.owner.login}/${item.repository.name}`; counter.set(name, (counter.get(name) ?? 0) + item.contributions.totalCount); } - }; - - countContributions(data.commitContributionsByRepository); - countContributions(data.pullRequestContributionsByRepository); - countContributions(data.issueContributionsByRepository); + } let top: { name: string; contributions: number } | null = null; for (const [name, contributions] of counter.entries()) { From 12fd61909ad60e3b4c3fd5d43a7a044d6470556e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 03:00:48 +0000 Subject: [PATCH 5/5] perf: Avoid unnecessary O(N) spread allocation in mergeTopRepository Co-authored-by: is0692vs <135803462+is0692vs@users.noreply.github.com> --- vitest.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/vitest.config.ts b/vitest.config.ts index 9404856..eba2a2b 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -13,6 +13,7 @@ export default defineConfig({ provider: "v8", reporter: ["text", "lcov"], include: [ + "src/lib/githubYearInReview.ts", "src/lib/**/*.ts", "src/hooks/**/*.ts", "src/components/ThemeController.tsx",