Skip to content

feat: version history page display download count#2178

Open
btea wants to merge 14 commits intonpmx-dev:mainfrom
btea:feat/history-versions-display-download
Open

feat: version history page display download count#2178
btea wants to merge 14 commits intonpmx-dev:mainfrom
btea:feat/history-versions-display-download

Conversation

@btea
Copy link
Copy Markdown
Contributor

@btea btea commented Mar 21, 2026

🔗 Linked issue

related to #2025

🧭 Context

The version history page displays the download count for each version over the past 7 days, consistent with the npmjs functionality, which seems very useful.

📚 Description

image

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 26, 2026 1:27am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 26, 2026 1:27am
npmx-lunaria Ignored Ignored Mar 26, 2026 1:27am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds server and client support to fetch and display per-version npm download counts. A new cached server API route server/api/registry/npmjs-versions/[...pkg].get.ts calls fetchNpmVersionDownloadsFromApi, normalises npm's /versions/{pkg}/last-week response, and returns package-level version download data. A new utility server/utils/npm-website-versions.ts implements the fetch and mapping logic. The frontend page app/pages/package/[[org]]/[name]/versions.vue lazily fetches the API, exposes memoised helpers for per-version and grouped downloads, and conditionally renders per-version and aggregated download counts with layout and guard adjustments.

Possibly related PRs

  • feat: add version history page #2025: Modifies the same app/pages/package/[[org]]/[name]/versions.vue and implements the version history layout that this change augments with per-version download fetching and display.

Suggested reviewers

  • shuuji3
  • ghostdevv
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description directly addresses the changeset by explaining the feature adds download count display to the version history page, with a screenshot demonstrating the implementation across featured versions and version groups.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 21, 2026

Codecov Report

❌ Patch coverage is 80.00000% with 7 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/pages/package/[[org]]/[name]/versions.vue 80.00% 7 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/pages/package/[[org]]/[name]/versions.vue (1)

295-302: Consider a shared download-count component.

The same lookup/format/label block now exists in five places, and the copies already differ slightly (div vs span, width classes, etc.). Pulling this into a tiny component or shared helper would make later styling or accessibility tweaks much easier to keep consistent.

Also applies to: 352-359, 443-450, 515-522, 563-570


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 15baadc8-c5b4-4bfe-827c-2d8460ab9ee2

📥 Commits

Reviewing files that changed from the base of the PR and between 8cd4074 and 6e6bbcb.

📒 Files selected for processing (3)
  • app/pages/package/[[org]]/[name]/versions.vue
  • server/api/registry/npmjs-versions/[...pkg].get.ts
  • server/utils/npm-website-versions.ts

@serhalp serhalp added the needs review This PR is waiting for a review from a maintainer label Mar 21, 2026
@MatteoGabriele
Copy link
Copy Markdown
Member

MatteoGabriele commented Mar 21, 2026

I personally found it hard to glance at these numbers without feeling a bit confused. What do you think about adding a "v" prefix to the version, or maybe in a separate tag? (The tag might be too bold, so I’m not sure; I would need to experiment with it.)

Screenshot 2026-03-21 at 19 53 16

perhaps even keeping the count always before the date, so that it's also visually consistent with the top block?

v1.2.0 230,000 Dec 24, 2023

@alex-key
Copy link
Copy Markdown
Contributor

alex-key commented Mar 22, 2026

I would do something like this as it's a pretty common pattern for other places on npmx when we show version resolution. Also it allows to have right section less busy with numbers.

image

@btea
Copy link
Copy Markdown
Contributor Author

btea commented Mar 22, 2026

Thank you for your suggestions, they all seem good. 👍

@ghostdevv ghostdevv marked this pull request as draft March 23, 2026 23:21
@ghostdevv
Copy link
Copy Markdown
Contributor

I'll mark this as draft while you work on it, can you mark it as ready when you want a review?

@btea
Copy link
Copy Markdown
Contributor Author

btea commented Mar 24, 2026

@ghostdevv Thank you for your comment.

Actually, the feature should already be complete. It's just that people have different suggestions regarding the information display location, and I'm not sure where would be most suitable. What do you think?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f04786c2-991f-4bb2-bb3b-7acd57fa06cb

📥 Commits

Reviewing files that changed from the base of the PR and between c12f507 and 9923d48.

📒 Files selected for processing (2)
  • app/pages/package/[[org]]/[name]/versions.vue
  • server/api/registry/npmjs-versions/[...pkg].get.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/api/registry/npmjs-versions/[...pkg].get.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
test/unit/server/utils/npm-website-versions.spec.ts (2)

12-14: Consider using vi.unstubAllGlobals() for proper cleanup.

vi.restoreAllMocks() restores mocked functions but does not unstub globals set via vi.stubGlobal(). This may cause test pollution if other tests in the suite rely on the original global values.

♻️ Proposed fix
 afterEach(() => {
   vi.restoreAllMocks()
+  vi.unstubAllGlobals()
 })

16-50: Good test coverage for scoped packages and 404 handling.

The tests verify URL encoding for scoped packages and the 404 error path. Consider adding coverage for:

  • Non-scoped package names (simpler encoding path)
  • The 502 error scenario when versionsResponse.ok is false but status is not 404
🧪 Additional test cases
it('handles non-scoped package names', async () => {
  const fetchMock = vi.fn().mockResolvedValue({
    ok: true,
    json: async () => ({
      downloads: { '2.0.0': 456 },
    }),
  })
  vi.stubGlobal('fetch', fetchMock)

  const result = await fetchNpmVersionDownloadsFromApi('lodash')

  expect(fetchMock).toHaveBeenCalledWith('https://api.npmjs.org/versions/lodash/last-week')
  expect(result).toEqual([{ version: '2.0.0', downloads: 456 }])
})

it('throws a 502 error when npm API returns a non-404 failure', async () => {
  const fetchMock = vi.fn().mockResolvedValue({
    ok: false,
    status: 500,
  })
  vi.stubGlobal('fetch', fetchMock)

  await expect(fetchNpmVersionDownloadsFromApi('some-package')).rejects.toMatchObject({
    statusCode: 502,
    message: 'Failed to fetch version download data from npm API',
  })
})
app/pages/package/[[org]]/[name]/versions.vue (3)

89-101: Consider memoising group download totals.

getGroupDownloads iterates over all versions in a group each time it's called. For packages with many versions per group (e.g., 216 versions in 2.x), this runs on every render. A memoised computed or a precomputed map keyed by groupKey would improve performance.

♻️ Proposed optimisation
+const groupDownloadsMap = computed(() => {
+  const map = new Map<string, number>()
+  for (const group of versionGroups.value) {
+    let total = 0
+    let hasValue = false
+    for (const version of group.versions) {
+      const downloads = versionDownloadsMap.value.get(version)
+      if (downloads !== undefined) {
+        total += downloads
+        hasValue = true
+      }
+    }
+    if (hasValue) map.set(group.groupKey, total)
+  }
+  return map
+})

-function getGroupDownloads(versions: string[]): number | undefined {
-  let total = 0
-  let hasValue = false
-
-  for (const version of versions) {
-    const downloads = getVersionDownloads(version)
-    if (downloads === undefined) continue
-    total += downloads
-    hasValue = true
-  }
-
-  return hasValue ? total : undefined
-}
+function getGroupDownloads(groupKey: string): number | undefined {
+  return groupDownloadsMap.value.get(groupKey)
+}

Then update template calls from getGroupDownloads(item.versions) to getGroupDownloads(item.groupKey).


582-583: Redundant fallback in :datetime binding.

The v-if guard already ensures item.versions[0] is truthy, making the ?? '' fallback unnecessary.

♻️ Suggested simplification
-:datetime="getVersionTime(item.versions[0] ?? '')!"
+:datetime="getVersionTime(item.versions[0])!"

20-27: Type definitions align with server response.

The interfaces correctly match the structure returned by the /api/registry/npmjs-versions/{package} endpoint. Consider importing NpmWebsiteVersionDownload directly from #server/utils/npm-website-versions to avoid duplication and ensure type consistency.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a950388d-c7ee-40a8-b8ae-bff668e7318b

📥 Commits

Reviewing files that changed from the base of the PR and between 9923d48 and a58326f.

📒 Files selected for processing (2)
  • app/pages/package/[[org]]/[name]/versions.vue
  • test/unit/server/utils/npm-website-versions.spec.ts

Copy link
Copy Markdown
Member

@MatteoGabriele MatteoGabriele left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of non-null assertions in this file; some of them are from previous work, but I wonder if they could be avoided, as it seems to me that they defeat the purpose of using TypeScript.

I like the adjustment of placing the count on the right side only; it looks much cleaner. However, could we add a brief explanation of what those numbers represent? It's not immediately clear to me. I understand they are downloads, given the PR title, but the UI doesn't specify this, and it might not be obvious to users either.
Perhaps 100,400 downloads?

thank you so much and let me know what you think 🙏

@btea
Copy link
Copy Markdown
Contributor Author

btea commented Mar 24, 2026

Thank you for reviewing.

Adding "downloads" after each download number seems a bit redundant. I've added a description column at the top; do you think that's feasible?

@MatteoGabriele
Copy link
Copy Markdown
Member

The issue is that the label isn't visible throughout the entire navigation; it disappears when I scroll. It also looks somewhat odd to me, especially with lower numbers and near the date, which I find confusing.

Screenshot 2026-03-24 at 13 21 33

Also found another issue: the download count in the header appears to be misplaced.
I'm not sure if that centered position is intentional or not.

Screenshot 2026-03-24 at 13 21 01

For me, these numbers without a label don't work, but maybe we could check if someone else has the same thoughts.

@btea
Copy link
Copy Markdown
Contributor Author

btea commented Mar 24, 2026

The issue is that the label isn't visible throughout the entire navigation; it disappears when I scroll. It also looks somewhat odd to me, especially with lower numbers and near the date, which I find confusing.

I think a description in the header is sufficient; it's not really necessary to display it all the time.

Also found another issue: the download count in the header appears to be misplaced. I'm not sure if that centered position is intentional or not.

Yeah, that was intentional. The top row feels spacious enough; I think it looks better in the middle. Do you think we should put it on the right, like the bottom row?

For me, these numbers without a label don't work, but maybe we could check if someone else has the same thoughts.

That's fine. My main concern is that there might not be enough space to include the relevant labels on the mobile app.

@MatteoGabriele
Copy link
Copy Markdown
Member

My final input. Thank you for being patient while I experiment with the UI here 🙏

Screenshot 2026-03-24 at 13 56 50 Screenshot 2026-03-24 at 14 13 00

Let's maybe call out @ghostdevv to see a third pair of eyes.

@btea
Copy link
Copy Markdown
Contributor Author

btea commented Mar 24, 2026

Adding an icon looks great, and we can also add a title hint. 👍

@MatteoGabriele
Copy link
Copy Markdown
Member

Adding an icon looks great, and we can also add a title hint. 👍

Yeah, also thinking about the a11y side of this: just a number wouldn't make much sense for a screen reader.

@MatteoGabriele
Copy link
Copy Markdown
Member

@btea, can we center the downloads, icon, and date? 🙏

Screenshot 2026-03-25 at 17 14 04

@ghostdevv
Copy link
Copy Markdown
Contributor

ghostdevv commented Mar 25, 2026

I also wonder if we could use a grid so that everything is aligned? This is what it looks like currently for vite:

image

@btea
Copy link
Copy Markdown
Contributor Author

btea commented Mar 26, 2026

Adjustments have been made as suggested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs review This PR is waiting for a review from a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants