Skip to content

Commit 8975844

Browse files
authored
Merge pull request #17174 from ethereum/perf/github-contributors-data-layer
Move GitHub contributors fetching to scheduled data-layer task
2 parents 2c6f141 + 4a897cf commit 8975844

File tree

59 files changed

+901
-424
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+901
-424
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Plan: Migrate GitHub Contributors to Data Layer
2+
3+
## Summary
4+
5+
Replace the current per-request GitHub API fetching with pre-computed data stored in Netlify Blobs via the existing data-layer infrastructure. This eliminates ~173K-347K API calls per build.
6+
7+
## Files to Delete (Previous Implementation)
8+
9+
- `src/scripts/github/getGitHubContributors.ts`
10+
- `src/data/github/contributors.json`
11+
- `src/data/github/app-contributors.json`
12+
- `.github/workflows/get-github-contributors.yml`
13+
14+
## Files to Create
15+
16+
### 1. `src/data-layer/fetchers/fetchGitHubContributors.ts`
17+
18+
New fetcher that:
19+
- Fetches contributors for all content files from GitHub API
20+
- Fetches contributors for all app pages
21+
- Returns `GitHubContributorsData` type
22+
- Follows existing fetcher patterns (logging, error handling, rate limiting)
23+
24+
```typescript
25+
export const FETCH_GITHUB_CONTRIBUTORS_TASK_ID = "fetch-github-contributors"
26+
27+
export async function fetchGitHubContributors(): Promise<GitHubContributorsData> {
28+
// Fetch all content file contributors
29+
// Fetch all app page contributors
30+
// Return combined data
31+
}
32+
```
33+
34+
### 2. `src/data-layer/mocks/fetch-github-contributors.json`
35+
36+
Mock data for local development with `USE_MOCK_DATA=true`.
37+
38+
## Files to Modify
39+
40+
### 1. `src/lib/types.ts`
41+
42+
Add type definition:
43+
```typescript
44+
export type GitHubContributorsData = {
45+
content: Record<string, FileContributor[]> // slug -> contributors
46+
appPages: Record<string, FileContributor[]> // pagePath -> contributors
47+
generatedAt: string
48+
}
49+
```
50+
51+
### 2. `src/data-layer/tasks.ts`
52+
53+
- Add import for `fetchGitHubContributors`
54+
- Add key: `GITHUB_CONTRIBUTORS: "fetch-github-contributors"`
55+
- Add to `DAILY` array: `[KEYS.GITHUB_CONTRIBUTORS, fetchGitHubContributors]`
56+
57+
### 3. `src/data-layer/index.ts`
58+
59+
Add getter:
60+
```typescript
61+
export const getGitHubContributors = () =>
62+
get<GitHubContributorsData>(KEYS.GITHUB_CONTRIBUTORS)
63+
```
64+
65+
### 4. `src/lib/data/index.ts`
66+
67+
Add cached wrapper:
68+
```typescript
69+
export const getGitHubContributors = createCachedGetter(
70+
dataLayer.getGitHubContributors,
71+
["github-contributors"],
72+
CACHE_REVALIDATE_DAY
73+
)
74+
```
75+
76+
### 5. `src/lib/utils/gh.ts`
77+
78+
- Remove the static JSON imports I added earlier
79+
- Remove `getStaticContentContributors` and `getStaticAppContributors`
80+
- Keep `fetchAndCacheGitHubContributors` as fallback for dev/new files
81+
82+
### 6. `src/lib/utils/contributors.ts`
83+
84+
Update to use data-layer:
85+
```typescript
86+
import { getGitHubContributors } from "@/lib/data"
87+
88+
export const getMarkdownFileContributorInfo = async (...) => {
89+
const contributorsData = await getGitHubContributors()
90+
let gitHubContributors = contributorsData?.content[slug] || null
91+
92+
// Fallback to API if not in data layer (new files during dev)
93+
if (!gitHubContributors) {
94+
gitHubContributors = await fetchAndCacheGitHubContributors(...)
95+
}
96+
// ... rest unchanged
97+
}
98+
99+
export const getAppPageContributorInfo = async (...) => {
100+
const contributorsData = await getGitHubContributors()
101+
let uniqueGitHubContributors = contributorsData?.appPages[pagePath] || null
102+
103+
// Fallback to API if not in data layer
104+
if (!uniqueGitHubContributors) {
105+
// ... existing API fetch logic
106+
}
107+
// ... rest unchanged
108+
}
109+
```
110+
111+
## Data Flow
112+
113+
```
114+
Trigger.dev (daily)
115+
116+
fetchGitHubContributors() - fetches from GitHub API
117+
118+
set(KEYS.GITHUB_CONTRIBUTORS, data) - stores in Netlify Blobs
119+
120+
Page render calls getGitHubContributors()
121+
122+
unstable_cache + React cache (request dedup)
123+
124+
storage.get() - retrieves from Netlify Blobs
125+
126+
contributors.ts uses data (zero API calls)
127+
```
128+
129+
## Implementation Order
130+
131+
1. Delete previous implementation files
132+
2. Add `GitHubContributorsData` type to `src/lib/types.ts`
133+
3. Create `src/data-layer/fetchers/fetchGitHubContributors.ts`
134+
4. Create `src/data-layer/mocks/fetch-github-contributors.json`
135+
5. Update `src/data-layer/tasks.ts` (key + import + DAILY registration)
136+
6. Update `src/data-layer/index.ts` (add getter)
137+
7. Update `src/lib/data/index.ts` (add cached wrapper)
138+
8. Update `src/lib/utils/gh.ts` (remove static imports/functions)
139+
9. Update `src/lib/utils/contributors.ts` (use data-layer)
140+
10. Run `pnpm lint:fix` and `npx tsc --noEmit`
141+
142+
## Notes
143+
144+
- **No filesystem access** in Trigger.dev - use GitHub Contents API to list files
145+
- Rate limiting: Use delays between requests (100-500ms)
146+
- App pages list: Predefined static list (changes infrequently)
147+
- Content files: Use GitHub API `GET /repos/{owner}/{repo}/contents/{path}` to recursively list `public/content/`
148+
149+
## GitHub API for File Discovery
150+
151+
```typescript
152+
// List directory contents recursively
153+
async function listContentFiles(path = "public/content"): Promise<string[]> {
154+
const url = `https://api.github.com/repos/ethereum/ethereum-org-website/contents/${path}`
155+
const response = await fetch(url, {
156+
headers: { Authorization: `token ${token}` }
157+
})
158+
const items = await response.json()
159+
160+
const slugs: string[] = []
161+
for (const item of items) {
162+
if (item.type === "dir" && item.name !== "translations") {
163+
// Recursively list subdirectories
164+
slugs.push(...await listContentFiles(item.path))
165+
} else if (item.name === "index.md") {
166+
// Found a content file, extract slug
167+
slugs.push(path.replace("public/content/", ""))
168+
}
169+
}
170+
return slugs
171+
}
172+
```

app/[locale]/10years/page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
setRequestLocale,
66
} from "next-intl/server"
77

8-
import type { CommitHistory, Lang, PageParams } from "@/lib/types"
8+
import type { Lang, PageParams } from "@/lib/types"
99

1010
import Emoji from "@/components/Emoji"
1111
import I18nProvider from "@/components/I18nProvider"
@@ -64,11 +64,9 @@ const Page = async ({ params }: { params: PageParams }) => {
6464
const innovationCards = await getInnovationCards()
6565
const adoptionCards = await getAdoptionCards()
6666

67-
const commitHistoryCache: CommitHistory = {}
6867
const { contributors } = await getAppPageContributorInfo(
6968
"10years",
70-
locale as Lang,
71-
commitHistoryCache
69+
locale as Lang
7270
)
7371

7472
return (

app/[locale]/apps/[application]/page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
setRequestLocale,
77
} from "next-intl/server"
88

9-
import type { ChainName, CommitHistory, Lang, PageParams } from "@/lib/types"
9+
import type { ChainName, Lang, PageParams } from "@/lib/types"
1010

1111
import AppCard from "@/components/AppCard"
1212
import ChainImages from "@/components/ChainImages"
@@ -131,11 +131,9 @@ const Page = async ({
131131
return new Date(app.dateOfLaunch).getFullYear()
132132
}
133133

134-
const commitHistoryCache: CommitHistory = {}
135134
const { contributors } = await getAppPageContributorInfo(
136135
"apps/[application]",
137-
locale as Lang,
138-
commitHistoryCache
136+
locale as Lang
139137
)
140138

141139
return (

app/[locale]/apps/categories/[catetgoryName]/page.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88

99
import {
1010
AppCategoryEnum,
11-
type CommitHistory,
1211
type Lang,
1312
type PageParams,
1413
type SectionNavDetails,
@@ -104,11 +103,9 @@ const Page = async ({
104103
})
105104
)
106105

107-
const commitHistoryCache: CommitHistory = {}
108106
const { contributors } = await getAppPageContributorInfo(
109107
"apps/categories/[catetgoryName]",
110-
locale as Lang,
111-
commitHistoryCache
108+
locale as Lang
112109
)
113110

114111
return (

app/[locale]/apps/page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
setRequestLocale,
66
} from "next-intl/server"
77

8-
import { CommitHistory, Lang, PageParams } from "@/lib/types"
8+
import { Lang, PageParams } from "@/lib/types"
99

1010
import AppCard from "@/components/AppCard"
1111
import Breadcrumbs from "@/components/Breadcrumbs"
@@ -67,11 +67,9 @@ const Page = async ({ params }: { params: PageParams }) => {
6767
const requiredNamespaces = getRequiredNamespacesForPage("/apps")
6868
const messages = pick(allMessages, requiredNamespaces)
6969

70-
const commitHistoryCache: CommitHistory = {}
7170
const { contributors } = await getAppPageContributorInfo(
7271
"apps",
73-
locale as Lang,
74-
commitHistoryCache
72+
locale as Lang
7573
)
7674

7775
return (

app/[locale]/assets/page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
setRequestLocale,
66
} from "next-intl/server"
77

8-
import type { CommitHistory, Lang, PageParams } from "@/lib/types"
8+
import type { Lang, PageParams } from "@/lib/types"
99

1010
import I18nProvider from "@/components/I18nProvider"
1111

@@ -26,11 +26,9 @@ export default async function Page({ params }: { params: PageParams }) {
2626
const requiredNamespaces = getRequiredNamespacesForPage("/assets")
2727
const messages = pick(allMessages, requiredNamespaces)
2828

29-
const commitHistoryCache: CommitHistory = {}
3029
const { contributors } = await getAppPageContributorInfo(
3130
"assets",
32-
locale as Lang,
33-
commitHistoryCache
31+
locale as Lang
3432
)
3533

3634
return (

app/[locale]/bug-bounty/page.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { getTranslations } from "next-intl/server"
22
import type { ComponentProps } from "react"
33

4-
import type { ChildOnlyProp, CommitHistory, Lang, Params } from "@/lib/types"
4+
import type { ChildOnlyProp, Lang, Params } from "@/lib/types"
55

66
/* Uncomment for Bug Bounty Banner: */
77
import Breadcrumbs from "@/components/Breadcrumbs"
@@ -115,13 +115,8 @@ export default async function Page({ params }: { params: Promise<Params> }) {
115115
const t = await getTranslations({ namespace: "page-bug-bounty" })
116116
const tCommon = await getTranslations({ namespace: "common" })
117117

118-
const commitHistoryCache: CommitHistory = {}
119118
const { contributors, lastEditLocaleTimestamp } =
120-
await getAppPageContributorInfo(
121-
"bug-bounty",
122-
locale as Lang,
123-
commitHistoryCache
124-
)
119+
await getAppPageContributorInfo("bug-bounty", locale as Lang)
125120

126121
const consensusBountyHunters: Node[] = consensusData.sort(sortBountyHuntersFn)
127122
const executionBountyHunters: Node[] = executionData.sort(sortBountyHuntersFn)

app/[locale]/collectibles/page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
setRequestLocale,
66
} from "next-intl/server"
77

8-
import type { CommitHistory, Lang, PageParams } from "@/lib/types"
8+
import type { Lang, PageParams } from "@/lib/types"
99

1010
import { HubHero } from "@/components/Hero"
1111
import I18nProvider from "@/components/I18nProvider"
@@ -56,11 +56,9 @@ export default async function Page({ params }: { params: PageParams }) {
5656
const requiredNamespaces = getRequiredNamespacesForPage("/collectibles/")
5757
const pickedMessages = pick(allMessages, requiredNamespaces)
5858

59-
const commitHistoryCache: CommitHistory = {}
6059
const { contributors } = await getAppPageContributorInfo(
6160
"collectibles",
62-
locale as Lang,
63-
commitHistoryCache
61+
locale as Lang
6462
)
6563

6664
return (

app/[locale]/community/events/page.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,7 @@ import {
88
} from "lucide-react"
99
import { getMessages, getTranslations } from "next-intl/server"
1010

11-
import type {
12-
CommitHistory,
13-
Lang,
14-
PageParams,
15-
SectionNavDetails,
16-
} from "@/lib/types"
11+
import type { Lang, PageParams, SectionNavDetails } from "@/lib/types"
1712

1813
import ContentHero from "@/components/Hero/ContentHero"
1914
import I18nProvider from "@/components/I18nProvider"
@@ -63,11 +58,9 @@ const Page = async ({ params }: { params: PageParams }) => {
6358
const requiredNamespaces = getRequiredNamespacesForPage("/community/events")
6459
const messages = pick(allMessages, requiredNamespaces)
6560

66-
const commitHistoryCache: CommitHistory = {}
6761
const { contributors } = await getAppPageContributorInfo(
6862
"community/events",
69-
locale as Lang,
70-
commitHistoryCache
63+
locale as Lang
7164
)
7265

7366
const events = mapEventTranslations(_events, t)

app/[locale]/community/page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
setRequestLocale,
66
} from "next-intl/server"
77

8-
import type { CommitHistory, Lang, PageParams } from "@/lib/types"
8+
import type { Lang, PageParams } from "@/lib/types"
99

1010
import I18nProvider from "@/components/I18nProvider"
1111

@@ -26,11 +26,9 @@ export default async function Page({ params }: { params: PageParams }) {
2626
const requiredNamespaces = getRequiredNamespacesForPage("/community")
2727
const pickedMessages = pick(allMessages, requiredNamespaces)
2828

29-
const commitHistoryCache: CommitHistory = {}
3029
const { contributors } = await getAppPageContributorInfo(
3130
"community",
32-
locale as Lang,
33-
commitHistoryCache
31+
locale as Lang
3432
)
3533

3634
return (

0 commit comments

Comments
 (0)