feat: wire Collection Contributors/Popularity/Development tabs to real widgets IN-1191#2013
feat: wire Collection Contributors/Popularity/Development tabs to real widgets IN-1191#2013gaspergrom wants to merge 5 commits into
Conversation
…l widgets IN-1191
Replaces the 3 stub collection tab views (a flat table + 2 metric
cards) with the same <lfx-widget-area> component project pages use,
so Collection detail pages show the full sidebar of widgets
(leaderboards, active contributors/organizations, dependency,
retention, geo-distribution, package downloads, PR metrics, etc.)
aggregated across every project in the collection.
Backend routing:
- Unifies /api/project/[slug]/{contributors,popularity,development}/*
into /api/widget/{contributors,popularity,development}/* (28 routes),
reading scope (project OR collectionSlug) from the query string via
a new getWidgetScope() helper instead of a route param, since scope
can now be either a single project or a whole collection.
- contributor-leaderboard is the one exception: it branches server-side
between the existing contributors_leaderboard pipe (project scope,
unchanged) and the new, separate, performance-optimized
collection_contributors_leaderboard pipe (collection scope) - see
that pipe's crowd.dev commit for why a naive collectionSlug port of
the single-project pipe wasn't viable (window-function aggregation
timed out at collection scale).
- Every other widget's data-layer function and Tinybird query type now
accepts collectionSlug alongside project, forwarding it straight
through to the already-deployed collectionSlug-branched pipes.
Frontend shell (mostly already in place from earlier this session):
- useProjectStore exposes isCollectionScope and short-circuits
repo/archived-repo-dependent computeds to safe values in collection
mode, since a collection has no single project's repos/widgets array.
- widget-area.vue filters the shown widgets by each widget config's new
availableInCollection flag (instead of a project's widgets array) and
skips the project-only benchmarks query entirely in collection mode.
- Every leaf widget component conditionally builds projectSlug vs
collectionSlug from the same isCollectionScope flag instead of always
assuming route.params.slug is a project.
Verified live against prod Tinybird: project-scoped calls unaffected
(regression-checked against real project data), collection-scoped
calls return correct aggregated data for a real 242-project collection
across contributors, popularity, and development widgets.
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
- Fix leaderboard drawers and share-badge feature silently querying the wrong scope (project instead of collectionSlug) on collection pages - Fix activity-type/platform filter dropdown never populating on collection pages (query gated on a null project slug) - Fix Contributor Dependency and Code Review Engagement widgets calling the project-only contributors_leaderboard pipe on collection pages - Fix per-widget benchmark tip-cards leaking onto collection pages with bogus point=0 data - Fix Only Linux Foundation projects toggle showing on non-Projects tabs - Fix metrics-row chip skeleton/content DOM-reuse causing clipped text - Fix redirect-to-Projects-tab guard for non-curated collections crashing during SSR, and harden it against a stale-cache race - Replicate project-menu.vue's responsive dropdown-fallback pattern in collection-menu.vue instead of a cramped scrollable tab row - Hide GitHub/Press/Social Mentions widgets on collection pages (mock data only, no real backend - project pages unaffected) Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this comment.
Pull request overview
Adds collection-scoped Contributors, Popularity, and Development widgets by reusing the project widget system and unified APIs.
Changes:
- Adds project-or-collection request scoping through frontend, API, and Tinybird layers.
- Enables supported widgets and collection-specific leaderboard pipes.
- Adds responsive collection navigation, date filtering, and scope-aware UI behavior.
Reviewed changes
Copilot reviewed 129 out of 129 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
frontend/types/development/requests.types.ts |
Adds collection scope to development filters. |
frontend/server/utils/common.ts |
Adds shared widget-scope parsing. |
frontend/server/data/types.ts |
Extends data filters for collections. |
frontend/server/data/tinybird/requests.types.ts |
Adds collection Tinybird query types. |
frontend/server/data/tinybird/popularity/search-queries.ts |
Forwards collection scope. |
frontend/server/data/tinybird/popularity/package-metrics.ts |
Forwards collection scope. |
frontend/server/data/tinybird/development/code-review-engagement.ts |
Adds collection leaderboard handling. |
frontend/server/data/tinybird/development/active-days.ts |
Forwards collection scope. |
frontend/server/data/tinybird/contributors/organizations-leaderboard.ts |
Supports collection queries. |
frontend/server/data/tinybird/contributors/contributors-dependency.ts |
Uses dedicated collection pipes. |
frontend/server/data/tinybird/contributors/collection-contributors-leaderboard.ts |
Adds collection leaderboard adapter. |
frontend/server/data/tinybird/contributors/active-organizations.ts |
Forwards collection scope. |
frontend/server/data/tinybird/contributors/active-contributors.ts |
Forwards collection scope. |
frontend/server/data/tinybird/activity-types.ts |
Supports collection activity types. |
frontend/server/data/data-sources.ts |
Registers collection leaderboard source. |
frontend/server/api/widget/popularity/stars.get.ts |
Unifies widget scope. |
frontend/server/api/widget/popularity/social-mentions.get.ts |
Updates scope documentation. |
frontend/server/api/widget/popularity/search-queries.get.ts |
Unifies widget scope. |
frontend/server/api/widget/popularity/press-mentions.get.ts |
Updates scope documentation. |
frontend/server/api/widget/popularity/packages.get.ts |
Unifies widget scope. |
frontend/server/api/widget/popularity/package-downloads.get.ts |
Unifies widget scope. |
frontend/server/api/widget/popularity/mailing-lists-messages.get.ts |
Unifies widget scope. |
frontend/server/api/widget/popularity/github-mentions.get.ts |
Updates scope documentation. |
frontend/server/api/widget/popularity/forks.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/review-time-by-pr-size.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/review-efficiency.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/pull-requests.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/patchsets-per-review.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/merge-lead-time.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/median-time-to-review.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/median-time-to-close.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/issues-resolution.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/contribution-outside.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/commit-activities.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/code-review-engagement.get.ts |
Unifies widget scope. |
frontend/server/api/widget/development/active-days.get.ts |
Unifies widget scope. |
frontend/server/api/widget/contributors/retention.get.ts |
Unifies widget scope. |
frontend/server/api/widget/contributors/organization-leaderboard.get.ts |
Unifies widget scope. |
frontend/server/api/widget/contributors/organization-dependency.get.ts |
Unifies widget scope. |
frontend/server/api/widget/contributors/geographical-distribution.get.ts |
Unifies widget scope. |
frontend/server/api/widget/contributors/contributor-leaderboard.get.ts |
Selects scope-specific leaderboard source. |
frontend/server/api/widget/contributors/contributor-dependency.get.ts |
Unifies widget scope. |
frontend/server/api/widget/contributors/activity-types.get.ts |
Unifies widget scope. |
frontend/server/api/widget/contributors/active-organizations.get.ts |
Unifies widget scope. |
frontend/server/api/widget/contributors/active-contributors.get.ts |
Unifies widget scope. |
frontend/app/pages/collection/details/[slug]/popularity.vue |
Simplifies collection view mounting. |
frontend/app/pages/collection/details/[slug]/development.vue |
Simplifies collection view mounting. |
frontend/app/pages/collection/details/[slug]/contributors.vue |
Simplifies collection view mounting. |
frontend/app/pages/collection/details/[slug].vue |
Adds dates and aggregate-tab guards. |
frontend/app/components/shared/modules/share/store/share.api.service.ts |
Makes badge requests scope-aware. |
frontend/app/components/shared/modules/share/components/active-contributor-badge.vue |
Selects collection or project scope. |
frontend/app/components/modules/widget/services/popularity.api.service.ts |
Uses unified popularity APIs. |
frontend/app/components/modules/widget/services/development.api.service.ts |
Uses unified development APIs. |
frontend/app/components/modules/widget/services/contributors.api.service.ts |
Uses unified contributor APIs. |
frontend/app/components/modules/widget/services/benchmarks.api.service.ts |
Supports disabling benchmark queries. |
frontend/app/components/modules/widget/config/widget.config.ts |
Adds collection availability metadata. |
frontend/app/components/modules/widget/config/popularity/stars/stars.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/popularity/stars/stars.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/popularity/social-mentions/social-mentions.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/popularity/social-mentions/social-mentions.config.ts |
Disables collection use. |
frontend/app/components/modules/widget/config/popularity/search-queries/search-queries.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/popularity/search-queries/search-queries.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/popularity/press-mentions/press-mentions.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/popularity/press-mentions/press-mentions.config.ts |
Disables collection use. |
frontend/app/components/modules/widget/config/popularity/package-downloads/package-downloads.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/popularity/package-downloads/package-downloads.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/popularity/package-dependency/package-dependency.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/popularity/package-dependency/package-dependency.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/popularity/mailing-list-messages/mailing-list-messages.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/popularity/mailing-list-messages/mailing-list-messages.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/popularity/github-mentions/github-mentions.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/popularity/github-mentions/github-mentions.config.ts |
Disables collection use. |
frontend/app/components/modules/widget/config/popularity/forks/forks.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/popularity/forks/forks.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/review-time-by-pull-request-size/review-time-by-pull-request-size.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/review-time-by-pull-request-size/review-time-by-pull-request-size.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/review-efficiency/review-efficiency.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/review-efficiency/review-efficiency.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/pull-requests/pull-requests.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/pull-requests/pull-requests.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/patchsets-per-review/patchsets-per-review.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/patchsets-per-review/patchsets-per-review.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/merge-lead-time/merge-lead-time.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/merge-lead-time/merge-lead-time.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/median-time-to-review/median-time-to-review.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/median-time-to-review/median-time-to-review.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/median-time-to-close/median-time-to-close.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/median-time-to-close/median-time-to-close.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/issues-resolution/issues-resolution.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/issues-resolution/issues-resolution.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/contributions-outside-work-hours/contributions-outside-work-hours.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/contributions-outside-work-hours/contributions-outside-work-hours.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/commit-activities/commit-activities.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/commit-activities/commit-activities.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/code-review-engagement/code-review-engagement.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/code-review-engagement/code-review-engagement.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/development/active-days/active-days.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/development/active-days/active-days.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/contributor/retention/retention.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/contributor/retention/retention.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/contributor/organizations-leaderboard/organizations-leaderboard.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/contributor/organizations-leaderboard/organizations-leaderboard.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/contributor/organizations-leaderboard/fragments/organization-leaderboard-drawer.vue |
Fixes drawer scope. |
frontend/app/components/modules/widget/config/contributor/organization-dependency/organization-dependency.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/contributor/organization-dependency/organization-dependency.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/contributor/geographical-distribution/geographical-distribution.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/contributor/geographical-distribution/geographical-distribution.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/contributor/contributors-leaderboard/fragments/contributor-leaderboard-drawer.vue |
Fixes drawer scope. |
frontend/app/components/modules/widget/config/contributor/contributors-leaderboard/contributors-leaderboard.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/contributor/contributors-leaderboard/contributors-leaderboard.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/contributor/contributor-dependency/contributor-dependency.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/contributor/contributor-dependency/contributor-dependency.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/contributor/active-organizations/active-organizations.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/contributor/active-organizations/active-organizations.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/config/contributor/active-contributors/active-contributors.vue |
Sends collection scope. |
frontend/app/components/modules/widget/config/contributor/active-contributors/active-contributors.config.ts |
Enables collection use. |
frontend/app/components/modules/widget/composables/usePopularityExcludedWidgets.ts |
Makes empty-data checks scope-aware. |
frontend/app/components/modules/widget/components/shared/widget.vue |
Hides collection benchmarks. |
frontend/app/components/modules/widget/components/shared/widget-area.vue |
Filters collection widgets and SSR queries. |
frontend/app/components/modules/widget/components/contributors/fragments/activities-dropdown.vue |
Loads collection activity filters. |
frontend/app/components/modules/project/store/project.store.ts |
Adds collection-aware derived state. |
frontend/app/components/modules/collection/views/collection-popularity.vue |
Replaces summary cards with widgets. |
frontend/app/components/modules/collection/views/collection-development.vue |
Replaces summary cards with widgets. |
frontend/app/components/modules/collection/views/collection-contributors.vue |
Replaces table with widgets. |
frontend/app/components/modules/collection/services/collections.api.service.ts |
Removes obsolete aggregate APIs. |
frontend/app/components/modules/collection/config/collection-links.ts |
Enables aggregate navigation links. |
frontend/app/components/modules/collection/components/details/header.vue |
Restricts project-only toggle. |
frontend/app/components/modules/collection/components/details/collection-metrics-row.vue |
Stabilizes rendered keys. |
frontend/app/components/modules/collection/components/details/collection-menu.vue |
Adds responsive tabs and date picker. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Whether this widget can appear on a Collection detail page's Contributors/Popularity/ | ||
| // Development tabs (aggregated across every project in the collection), in addition to | ||
| // individual Project pages. Collections have no per-project `widgets` array to check | ||
| // against, so this is an explicit opt-in rather than inherited from project data. | ||
| availableInCollection?: boolean; |
There was a problem hiding this comment.
Good catch on the gap, though it's a degraded-UI issue rather than a crash: widget.vue already guards both call sites against a null project - the description block is wrapped in v-if="project" (so it just doesn't render rather than throwing), and getDefaultValue() already does project.value ? defaultValue(project.value) : {}. So collection widgets currently render with no description text and empty computed defaults, but they don't break.
Building proper collection-aware descriptions/defaults would mean touching the WidgetConfig contract and rewriting description/defaultValue for every one of the ~26 availableInCollection widgets individually - a much larger, cross-cutting change than fits in this fix round. Filing this as a scoped follow-up rather than doing it here; happy to prioritize it if the no-description state is worse for users than I'm assuming.
- project.store.ts: reset project/collaborationSet when entering collection scope, so a client-side nav from a project page no longer leaves stale project data behind for widgets that read the store directly (description, defaultValue, embed/share). - widget-menu.vue: disable the Embed menu action in collection scope - its URL is built from project.value?.slug, which is undefined there and has no collection embed route yet. - collection/details/[slug].vue: also watch isAggregateWidgetTab (not just the query data) in the non-curated redirect guard, since the parent layout stays mounted across child-tab navigation and a data-only watcher never re-runs the guard for a client-side tab switch after data has already loaded. - common.ts getWidgetScope: reject array values for project/ collectionSlug (e.g. ?project=a&project=b) instead of silently passing the exclusivity check with an array bound to a string-typed field. - collection-contributors-leaderboard.ts: forward activity_types (plural) alongside activity_type to both the data and count queries, matching the Tinybird query type contract. - Added missing test coverage: collection-contributors-leaderboard.ts had no test file; contributors-dependency.test.ts and code-review-engagement.test.ts only covered the project-scoped path, not collectionSlug. Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
| if (!dateQueryParams.value.timeRange) { | ||
| selectedTimeRangeKey.value = defaultTimeRangeKey; | ||
| startDate.value = defaultDateOption?.startDate || null; | ||
| endDate.value = defaultDateOption?.endDate || null; | ||
|
|
||
| dateQueryParams.value = { | ||
| timeRange: selectedTimeRangeKey.value, | ||
| start: startDate.value, | ||
| end: endDate.value, | ||
| }; | ||
| } |
| key: 'searchQueries', | ||
| availableInCollection: true, | ||
| name: 'Search queries volume', | ||
| description: (project) => `Search volume for "${project.name}" on Google Search.`, |
…velocity IN-1191 Fixes found during Collections v2 prod QA: - Header metric chips stayed stuck on loading skeletons after refreshing on a sub-tab (Contributors/Popularity/Development). The metrics query was never awaited in onServerPrefetch, so SSR shipped isMetricsLoading=true and the client never re-ran it. Await metricsSuspense() during SSR. - Selected date range reset to the default when switching between the in-depth tabs, because the tab links dropped the route query. Preserve query (timeRange/ start/end, onlyLFProjects) in collection-menu links, mirroring project-menu. - Development tab's Issues-resolution and Pull-requests widgets rendered a null avgVelocityInDays as a literal 'Avg. velocity: 0 seconds' (collection-scoped pipe returns null). Render '—' instead of a fake zero value. Claude-Session: https://claude.ai/code/session_014Fc86j8FLeWFAs3fDCQs38 Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
| */ | ||
| const projectSlug = computed(() => route.params.slug as string); | ||
| const projectSlug = computed(() => (isCollectionScope.value ? undefined : (route.params.slug as string))); | ||
| const collectionSlug = computed(() => (isCollectionScope.value ? (route.params.slug as string) : undefined)); |
| // Resolve the header metrics query during SSR too, otherwise on a sub-tab refresh | ||
| // (Contributors/Popularity/Development) the server ships isMetricsLoading=true and the | ||
| // client never re-runs the query, leaving the header metric chips stuck on skeletons. | ||
| // Awaited after the 404 check so a metrics failure never blocks the collection 404 path; | ||
| // metrics.ts already degrades gracefully on upstream error, so this can't hang the page. | ||
| await metricsSuspense(); |
…ets IN-1191 The Issues-resolution, Pull-requests and Merge-lead-time server handlers accessed velocity.data[0].<field> without guarding the array. When Tinybird returns an empty data array (no resolved issues/PRs in scope — common for collection-scoped or slow SSR requests on large collections), data[0] is undefined and the property access threw, dropping the value from the widget response — surfacing as 'Avg. velocity: —' despite the metric being computable, and merge-lead-time rendering '0 seconds'. Guard every .data[0] access with optional chaining and a null fallback, and make avgVelocityInDays nullable end-to-end so the frontend renders '—' honestly instead of a fabricated 0 or crashing the widget. Verified locally: real values still render (pytorch issues '1 month', PRs '15 days'), and an empty date window returns null cleanly with no crash. Claude-Session: https://claude.ai/code/session_014Fc86j8FLeWFAs3fDCQs38 Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 137 out of 137 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
frontend/app/pages/collection/details/[slug].vue:102
- A collection URL's explicit date range is never copied into the existing Pinia store. After SPA navigation, this branch only handles the missing-query case, so the date picker mounts with the previous page's range and its immediate watcher overwrites the requested URL. Synchronize the store from
dateQueryParamswhentimeRangeis present, and only write defaults when it is absent.
| const currentValue = current?.openedToMergedSeconds ?? null; | ||
| const previousValue = previous?.openedToMergedSeconds ?? null; | ||
| const currentToReviewAssigned = current?.openedToReviewAssignedSeconds ?? null; | ||
| const previousToReviewAssigned = previous?.openedToReviewAssignedSeconds ?? null; | ||
| const currentToFirstReview = current?.reviewAssignedToFirstReviewSeconds ?? null; | ||
| const previousToFirstReview = previous?.reviewAssignedToFirstReviewSeconds ?? null; | ||
| const currentToApproved = current?.firstReviewToApprovedSeconds ?? null; | ||
| const previousToApproved = previous?.firstReviewToApprovedSeconds ?? null; | ||
| const currentToMerged = current?.approvedToMergedSeconds ?? null; | ||
| const previousToMerged = previous?.approvedToMergedSeconds ?? null; | ||
|
|
||
| // Only meaningful when both periods have data; comparing against a null (no-data) period would | ||
| // be misleading, so treat those as neutral. | ||
| const changeType = (curr: number | null, prev: number | null): 'positive' | 'negative' => | ||
| curr !== null && prev !== null && curr > prev ? 'positive' : 'negative'; |
| // Collections have no per-project `widgets` array to check against (see widget-area.vue's | ||
| // identical isWidgetShown branch) - availableInCollection already gated these widgets into | ||
| // the sidebar, so the only remaining check here is the emptiness one below. | ||
| return isCollectionScope || projectWidgets.includes(lfxWidgets[widget]?.key); |
Summary
Collection detail pages (
/collection/details/[slug]/{contributors,popularity,development}) now render the same widget system as individual Project pages — leaderboards, dependency charts, retention, geo-distribution, package/download widgets, PR metrics, etc. — aggregated across every project in the collection, instead of scoped to one project.<lfx-widget-area>and the existing widget config system rather than building separate UI. Each widget config gets anavailableInCollectionflag controlling visibility on collection pages./api/widget/<domain>/<name>.get.ts, scoped via query string (?project=or?collectionSlug=) through a sharedgetWidgetScope()helper.collectionSlugalongsideproject(viasegments_filtered_by_collection). Two widgets (Contributors Leaderboard, Contributor Dependency) get dedicated collection-scoped pipes for performance, since a naive branch on the single-project pipe's window-function math times out at collection scale (16s+ on a 242-project collection).Dependencies
Requires the following crowd.dev Tinybird pipes to be deployed (already live in prod as of this PR):
collection_contributors_leaderboard.pipecollection_contributor_dependency.pipesegments_filtered_by_collection.pipeactivityTypes_by_project.pipe(collectionSlug branch)collectionSlug(active_contributors, organizations_leaderboard, package_metrics, etc.)See crowd.dev PR #4348 for the pipe-side changes.
Test plan
pnpm tsc-checkcleanpnpm lintclean (9 pre-existing unrelated warnings)pnpm test— 131/131 passing