-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(pages): show live npm downloads in highlights stats #794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
ff4feba
b8e0830
72cd50d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,63 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copyright 2026 alibaba/open-code-review Contributors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Period = 'last-day' | 'last-week' | 'last-month' | 'last-year'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface NpmDownloadsState { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| downloads: number | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loading: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 实时获取某个 npm 包的下载量。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 数据源为 npm 官方统计 API(支持 CORS,可在纯静态页面直接调用)。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 请求失败时 error 为 true,调用方可据此优雅降级。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 当前用于 HighlightsSection 的「NPM 社区下载量」指标:请求进行中或失败时, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 组件回退到 i18n 中的兜底静态值。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function useNpmDownloads(pkg: string, period: Period = 'last-month'): NpmDownloadsState { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [state, setState] = useState<NpmDownloadsState>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| downloads: null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loading: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let cancelled = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setState({ downloads: null, loading: true, error: false }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 弱网或 API 无响应时,超时后 abort 请求并降级,避免界面长期卡在 loading | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const controller = new AbortController(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const timeout = setTimeout(() => controller.abort(), 8000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // pkg 可能是 scoped 包名(含 `/`),编码后再插值以保证 URL 路径合法 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch(`https://api.npmjs.org/downloads/point/${period}/${encodeURIComponent(pkg)}`, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [maintainability · medium] Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| signal: controller.signal, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then((r) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!r.ok) throw new Error(`npm downloads API responded ${r.status}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return r.json(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then((data: { downloads?: number }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (cancelled) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof data.downloads !== 'number') throw new Error('unexpected payload'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setState({ downloads: data.downloads, loading: false, error: false }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .catch(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (cancelled) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setState({ downloads: null, loading: false, error: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [maintainability · medium] Suggestion:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cancelled = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clearTimeout(timeout); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| controller.abort(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [pkg, period]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return state; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,14 +25,14 @@ export const en = { | |
|
|
||
| // Highlights | ||
| 'highlights.stat1Value': '20K+', | ||
| 'highlights.stat1Label': 'ACTIVE USERS', | ||
| 'highlights.stat1Label': 'INTERNAL ACTIVE USERS', | ||
| 'highlights.stat1Caption': 'Battle-tested inside Alibaba Group', | ||
| 'highlights.stat2Value': '> 30%', | ||
| 'highlights.stat2Label': 'ADOPTION RATE', | ||
| 'highlights.stat2Caption': 'Battle-tested inside Alibaba Group', | ||
| 'highlights.stat3Value': '1M+', | ||
| 'highlights.stat2Value': '150K+', | ||
| 'highlights.stat2Label': 'NPM COMMUNITY DOWNLOADS', | ||
| 'highlights.stat2Caption': 'Real npm downloads · last 30 days', | ||
| 'highlights.stat3Value': '3M+', | ||
| 'highlights.stat3Label': 'REAL-WORLD TASKS', | ||
| 'highlights.stat3Caption': 'Code review tasks executed', | ||
| 'highlights.stat3Caption': 'Battle-tested inside Alibaba Group', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [maintainability · medium] |
||
| 'highlights.stat4Value': '1/9', | ||
| 'highlights.stat4Label': 'TOKEN COST', | ||
| 'highlights.stat4Caption': 'vs. Claude Code · 1,000 PRs', | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,14 +27,14 @@ export const ja: TranslationKeys = { | |
|
|
||
| // Highlights | ||
| 'highlights.stat1Value': '20K+', | ||
| 'highlights.stat1Label': 'アクティブユーザー', | ||
| 'highlights.stat1Label': '社内アクティブユーザー', | ||
| 'highlights.stat1Caption': 'Alibabaグループ内で実戦検証済み', | ||
| 'highlights.stat2Value': '> 30%', | ||
| 'highlights.stat2Label': '採用率', | ||
| 'highlights.stat2Caption': 'Alibabaグループ内で実戦検証済み', | ||
| 'highlights.stat3Value': '1M+', | ||
| 'highlights.stat2Value': '150K+', | ||
| 'highlights.stat2Label': 'NPM コミュニティダウンロード', | ||
| 'highlights.stat2Caption': 'npm 過去30日の実ダウンロード数', | ||
| 'highlights.stat3Value': '3M+', | ||
| 'highlights.stat3Label': '実タスク', | ||
| 'highlights.stat3Caption': '実行されたコードレビュータスク', | ||
| 'highlights.stat3Caption': 'Alibabaグループ内で実戦検証済み', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [maintainability · low]
Also, note that |
||
| 'highlights.stat4Value': '1/9', | ||
| 'highlights.stat4Label': 'トークンコスト', | ||
| 'highlights.stat4Caption': 'Claude Code との比較 · 1,000 PR', | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -27,14 +27,14 @@ export const ru: TranslationKeys = { | |||||
|
|
||||||
| // Highlights | ||||||
| 'highlights.stat1Value': '20K+', | ||||||
| 'highlights.stat1Label': 'АКТИВНЫХ ПОЛЬЗОВАТЕЛЕЙ', | ||||||
| 'highlights.stat1Label': 'ВНУТРЕННИЕ АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ', | ||||||
| 'highlights.stat1Caption': 'в Alibaba Group', | ||||||
| 'highlights.stat2Value': '> 30%', | ||||||
| 'highlights.stat2Label': 'ЗАМЕЧАНИЙ ПРИНЯТО', | ||||||
| 'highlights.stat2Caption': 'в Alibaba Group', | ||||||
| 'highlights.stat3Value': '1M+', | ||||||
| 'highlights.stat2Value': '150K+', | ||||||
| 'highlights.stat2Label': 'ЗАГРУЗКИ СООБЩЕСТВА NPM', | ||||||
| 'highlights.stat2Caption': 'реальные загрузки npm · 30 дней', | ||||||
| 'highlights.stat3Value': '3M+', | ||||||
| 'highlights.stat3Label': 'ЗАДАЧ КОД-РЕВЬЮ', | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [other · medium] Suggestion:
Suggested change
|
||||||
| 'highlights.stat3Caption': 'выполнено в реальных проектах', | ||||||
| 'highlights.stat3Caption': 'в Alibaba Group', | ||||||
| 'highlights.stat4Value': '1/9', | ||||||
| 'highlights.stat4Label': 'ОТ РАСХОДА ТОКЕНОВ', | ||||||
| 'highlights.stat4Caption': 'Claude Code · 1 000 PR', | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,14 +27,14 @@ export const zh: TranslationKeys = { | |
|
|
||
| // Highlights | ||
| 'highlights.stat1Value': '20K+', | ||
| 'highlights.stat1Label': '活跃用户', | ||
| 'highlights.stat1Label': '内部活跃用户', | ||
| 'highlights.stat1Caption': '经阿里巴巴集团内部实战验证', | ||
| 'highlights.stat2Value': '> 30%', | ||
| 'highlights.stat2Label': '采纳率', | ||
| 'highlights.stat2Caption': '经阿里巴巴集团内部实战验证', | ||
| 'highlights.stat3Value': '1M+', | ||
| 'highlights.stat2Value': '150K+', | ||
| 'highlights.stat2Label': 'NPM 社区下载量', | ||
| 'highlights.stat2Caption': 'npm 近 30 天真实下载', | ||
| 'highlights.stat3Value': '3M+', | ||
| 'highlights.stat3Label': '真实任务', | ||
| 'highlights.stat3Caption': '已执行的代码审查任务', | ||
| 'highlights.stat3Caption': '经阿里巴巴集团内部实战验证', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [bug · medium] Previously, stat3Caption had a unique value ("已执行的代码审查任务") that described the stat. It seems likely that stat3Caption should have its own distinct caption rather than duplicating stat1Caption. Please verify this is intentional, or provide a unique caption for stat3. |
||
| 'highlights.stat4Value': '1/9', | ||
| 'highlights.stat4Label': 'TOKEN 成本', | ||
| 'highlights.stat4Caption': '对比 Claude Code · 1,000 个 PR', | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[bug · medium]
Resource leak:
setTimeoutnot cleared on fetch completion. The 8-second timeout is only cleared in the effect cleanup function. If the fetch resolves or rejects well before the timeout, the timer keeps running and will callcontroller.abort()on an already-finished request. While functionally harmless, it's a minor resource leak. Clear the timeout in both success and error paths (or in afinallyblock).