Skip to content

Commit 91aa11e

Browse files
authored
feat: scope widget pipes by collection for aggregate tabs (IN-1195) (#4348)
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
1 parent a21602d commit 91aa11e

14 files changed

Lines changed: 318 additions & 38 deletions

services/libs/tinybird/pipes/active_contributors.pipe

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ DESCRIPTION >
44
- **When `granularity` is NOT provided, returns total unique contributor count** for the specified filters.
55
- **When `granularity` is provided, returns time-series data** showing unique contributor counts for each time period.
66
- Parameters:
7-
- `project`: Required string for project slug (e.g., 'k8s', 'tensorflow') - inherited from `segments_filtered`
7+
- `project`: Required string for project slug (e.g., 'k8s', 'tensorflow') - inherited from `segments_filtered`. Required unless `collectionSlug` is given.
8+
- `collectionSlug`: Collection slug (e.g., 'cncf') - inherited from `segments_filtered_by_collection`. Required unless `project` is given.
89
- `repos`: Optional array of repository URLs for filtering (inherited from `segments_filtered`)
910
- `startDate`: Optional DateTime filter for activities after timestamp (e.g., '2024-01-01 00:00:00')
1011
- `endDate`: Optional DateTime filter for activities before timestamp (e.g., '2024-12-31 23:59:59')
@@ -55,7 +56,10 @@ SQL >
5556
SELECT uniqIf(memberId, memberId != '') AS maintainerCount
5657
FROM maintainers_roles_copy_ds
5758
WHERE
58-
insightsProjectId = (SELECT insightsProjectId FROM segments_filtered) AND role = 'maintainer'
59+
{% if defined(collectionSlug) %}
60+
insightsProjectId IN (SELECT insightsProjectId FROM segments_filtered_by_collection)
61+
{% else %} insightsProjectId = (SELECT insightsProjectId FROM segments_filtered)
62+
{% end %} AND role = 'maintainer'
5963
{% if defined(repos) %}
6064
AND repoUrl
6165
IN {{ Array(repos, 'String', description="Filter maintainer repo list", required=False) }}

services/libs/tinybird/pipes/activities_filtered.pipe

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ DESCRIPTION >
22
- `activities_filtered.pipe` is the core filtering infrastructure pipe for activity data across the entire analytics platform.
33
- This pipe serves as the foundation for most activity-related widgets, by providing a consistent, filtered view of contribution activities.
44
- It filters activities from `activityRelations_deduplicated_cleaned_ds` datasource based on project segment, time ranges, repositories, platforms, and activity types.
5-
- The pipe automatically scopes data to the current project using `segments_filtered` pipe for security and data isolation.
5+
- The pipe scopes data either to a single project (via `segments_filtered`) or to every project in a collection
6+
(via `segments_filtered_by_collection`), for security and data isolation. Exactly one of `project`/`collectionSlug`
7+
must be provided by the caller.
68
- Parameters:
7-
- `project`: Inherited from `segments_filtered`, project slug (e.g., 'k8s', 'tensorflow')
9+
- `project`: Project slug (e.g., 'k8s', 'tensorflow') - inherited from `segments_filtered`. Required unless `collectionSlug` is given.
10+
- `collectionSlug`: Collection slug (e.g., 'cncf') - inherited from `segments_filtered_by_collection`. Required unless `project` is given.
811
- `repos`: Optional array of repository URLs for filtering (e.g., ['https://github.com/kubernetes/kubernetes']). Inherited from `segments_filtered`.
912
- `startDate`: Optional DateTime filter for activities after timestamp (e.g., '2024-01-01 00:00:00')
1013
- `endDate`: Optional DateTime filter for activities before timestamp (e.g., '2024-12-31 23:59:59')
@@ -21,9 +24,15 @@ NODE activities_filtered_bucket_routing
2124
SQL >
2225
%
2326
SELECT activityId as id, timestamp, type, platform, memberId, organizationId, segmentId
24-
FROM activityRelations_bucket_routing a
27+
FROM
28+
{% if defined(collectionSlug) %} activityRelations_deduplicated_cleaned_bucket_union
29+
{% else %} activityRelations_bucket_routing
30+
{% end %} as a
2531
where
26-
segmentId = (SELECT segmentId FROM segments_filtered)
32+
{% if defined(collectionSlug) %}
33+
segmentId IN (SELECT segmentId FROM segments_filtered_by_collection)
34+
{% else %} segmentId = (SELECT segmentId FROM segments_filtered)
35+
{% end %}
2736
{% if defined(startDate) %}
2837
AND a.timestamp
2938
> {{ DateTime(startDate, description="Filter activity timestamp after", required=False) }}

services/libs/tinybird/pipes/activities_filtered_retention.pipe

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ DESCRIPTION >
55
- This time extension ensures retention rate calculations have the necessary baseline data from the preceding period for comparing distinct member activity for the first given period.
66
- Primary use case: providing data foundation for member retention analysis and cohort studies.
77
- Parameters:
8-
- `project`: Inherited from `segments_filtered`, project slug (e.g., 'k8s', 'tensorflow')
8+
- `project`: Inherited from `segments_filtered`, project slug (e.g., 'k8s', 'tensorflow'). Required unless `collectionSlug` is given.
9+
- `collectionSlug`: Inherited from `segments_filtered_by_collection`, collection slug (e.g., 'cncf'). Required unless `project` is given.
910
- `repos`: Inherited from `segments_filtered`, array of repository URLs for filtering
1011
- `startDate`: Optional DateTime filter, automatically extended backwards by one granularity period
1112
- `endDate`: Optional DateTime filter for activities before timestamp (e.g., '2024-12-31 23:59:59')
@@ -22,9 +23,15 @@ NODE activities_filtered_retention_bucket_routing
2223
SQL >
2324
%
2425
SELECT activityId as id, timestamp, type, platform, memberId, organizationId, segmentId
25-
FROM activityRelations_bucket_routing a
26+
FROM
27+
{% if defined(collectionSlug) %} activityRelations_deduplicated_cleaned_bucket_union
28+
{% else %} activityRelations_bucket_routing
29+
{% end %} as a
2630
where
27-
segmentId = (SELECT segmentId FROM segments_filtered)
31+
{% if defined(collectionSlug) %}
32+
segmentId IN (SELECT segmentId FROM segments_filtered_by_collection)
33+
{% else %} segmentId = (SELECT segmentId FROM segments_filtered)
34+
{% end %}
2835
{% if defined(startDate) %}
2936
AND a.timestamp
3037
> {% if defined(granularity) and granularity == "daily" %}
Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
DESCRIPTION >
2-
- `activityTypes_by_project.pipe` returns activity types that actually exist for a given project.
2+
- `activityTypes_by_project.pipe` returns activity types that actually exist for a given project, or
3+
across every project in a collection.
34
- Unlike `activityTypes_filtered` which returns all possible activity types, this pipe only returns types with actual activities in the project.
45
- Useful for populating dropdowns, filters, or analytics that should only show activity types with data.
56
- Parameters:
6-
- `project`: **Required** string for project slug (e.g., 'k8s', 'tensorflow'). Passed to `segments_filtered`.
7+
- `project`: String for project slug (e.g., 'k8s', 'tensorflow'). Passed to `segments_filtered`. Required unless `collectionSlug` is given.
8+
- `collectionSlug`: Collection slug (e.g., 'cncf') - inherited from `segments_filtered_by_collection`. Required unless `project` is given.
9+
Collection requests read from `activityRelations_deduplicated_cleaned_bucket_union` (all 10 buckets) instead
10+
of `activityRelations_bucket_routing` (a single bucket, defaulting to bucket 0 when `bucketId` is not
11+
injected), since a collection's member projects' segments can be routed to any of the 10 buckets - same
12+
pattern as `activities_filtered.pipe`.
713
- `repos`: Optional array of repository URLs for filtering (e.g., ['https://github.com/kubernetes/kubernetes']). Filters activities by repository.
814
- `includeCodeContributions`: Optional boolean to include code contribution activities. Defaults to 1. Set to 0 to exclude. Passed to `activityTypes_filtered`.
915
- `includeCollaborations`: Optional boolean to include or exclude collaboration activities. Defaults to 0. Passed to `activityTypes_filtered`.
@@ -15,10 +21,16 @@ NODE activityTypes_by_project_0
1521
SQL >
1622
%
1723
SELECT DISTINCT a.type as activityType, a.platform, at.label
18-
FROM activityRelations_bucket_routing a
24+
FROM
25+
{% if defined(collectionSlug) %} activityRelations_deduplicated_cleaned_bucket_union
26+
{% else %} activityRelations_bucket_routing
27+
{% end %} a
1928
INNER JOIN activityTypes at ON a.type = at.activityType AND a.platform = at.platform
2029
WHERE
21-
a.segmentId = (SELECT segmentId FROM segments_filtered)
30+
{% if defined(collectionSlug) %}
31+
a.segmentId IN (SELECT segmentId FROM segments_filtered_by_collection)
32+
{% else %} a.segmentId = (SELECT segmentId FROM segments_filtered)
33+
{% end %}
2234
{% if defined(repos) %} AND a.channel IN (SELECT channel FROM repos_to_channels) {% end %}
2335
AND (a.type, a.platform) IN (SELECT activityType, platform FROM activityTypes_filtered)
2436
ORDER BY activityType, platform

services/libs/tinybird/pipes/activity_heatmap_by_weekday_and_2hours_blocks.pipe

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ DESCRIPTION >
55
- Returns a complete grid of all weekday/time combinations with zero counts for periods with no activity, ensuring consistent heatmap visualization.
66
- Primary use case: identifying contribution patterns outside traditional work hours for community health analysis.
77
- Parameters:
8-
- `project`: Inherited from `segments_filtered`, project slug (e.g., 'k8s', 'tensorflow')
8+
- `project`: Inherited from `segments_filtered`, project slug (e.g., 'k8s', 'tensorflow'). Required unless `collectionSlug` is given.
9+
- `collectionSlug`: Inherited from `segments_filtered_by_collection`, collection slug (e.g., 'cncf'). Required unless `project` is given.
910
- `repos`: Inherited from `segments_filtered`, array of repository URLs for filtering
1011
- `startDate`: Optional DateTime filter for activities after timestamp (e.g., '2024-01-01 00:00:00')
1112
- `endDate`: Optional DateTime filter for activities before timestamp (e.g., '2024-12-31 23:59:59')
@@ -18,7 +19,10 @@ SQL >
1819
select count(id) as activityCount, weekday, two_hours_block
1920
from contributions_with_local_time_ds a
2021
where
21-
segmentId = (SELECT segmentId FROM segments_filtered)
22+
{% if defined(collectionSlug) %}
23+
segmentId IN (SELECT segmentId FROM segments_filtered_by_collection)
24+
{% else %} segmentId = (SELECT segmentId FROM segments_filtered)
25+
{% end %}
2226
{% if defined(startDate) %}
2327
AND a.timestamp
2428
> {{ DateTime(startDate, description="Filter activity timestamp after", required=False) }}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
DESCRIPTION >
2+
- `collection_contributor_dependency.pipe` serves the contributor dependency widget for a
3+
collection's Contributors tab - identifies contributors whose combined contributions reach or
4+
exceed 51% of total collection activity (bus factor analysis), aggregated across every project
5+
in the collection.
6+
- Collection-scoped counterpart to `contributor_dependency.pipe` - separate because that pipe
7+
references `contributors_leaderboard` (project-only) by name, and the collection-scale
8+
replacement for that table is `collection_contributors_leaderboard` (a differently-shaped
9+
pipe - see its own DESCRIPTION for why a naive collectionSlug branch on the single-project
10+
leaderboard pipe isn't used there either).
11+
- Combines data from `collection_contributors_leaderboard` and `active_contributors` (both
12+
already collectionSlug-aware) to provide dependency risk assessment at collection scale.
13+
- Parameters:
14+
- `collectionSlug`: Required collection slug (e.g., 'cncf') - inherited from `segments_filtered_by_collection`.
15+
- `repos`: Optional array of repository URLs for filtering (inherited from `segments_filtered_by_collection`)
16+
- `startDate`: Optional DateTime filter for activities after timestamp (e.g., '2024-01-01 00:00:00')
17+
- `endDate`: Optional DateTime filter for activities before timestamp (e.g., '2024-12-31 23:59:59')
18+
- `platform`: Optional string filter for source platform (e.g., 'github', 'discord', 'slack')
19+
- `activity_type`: Optional string filter for single activity type (e.g., 'authored-commit')
20+
- `includeCollaborations`: Optional boolean, defaults to false - same toggle as the single-project pipe.
21+
- `limit`: Optional integer for the number of top contributors to rank before finding the 51%
22+
cutoff. Inherited by `collection_contributors_leaderboard` (this pipe has no default of its
23+
own), whose own default is 10 if omitted - callers doing bus-factor analysis should pass
24+
limit=100 explicitly (same convention as the single-project `contributor_dependency.pipe` /
25+
`contributors_leaderboard.pipe` pair) to reliably reach the 51% cutoff.
26+
- Response: `id`, `displayName`, `githubHandleArray`, `contributionCount`, `contributionPercentage`, `roles`, `contributionPercentageRunningTotal`, `totalContributorCount`
27+
28+
TAGS "Insights, Widget", "Collection", "Contributors"
29+
30+
NODE collection_contributions_percentage_running_total
31+
SQL >
32+
%
33+
SELECT t.*, active_contributors.contributorCount as "totalContributorCount"
34+
FROM
35+
(
36+
SELECT
37+
id,
38+
displayName,
39+
githubHandleArray,
40+
contributionCount,
41+
contributionPercentage,
42+
roles,
43+
sum(contributionPercentage) OVER (
44+
ORDER BY contributionPercentage DESC, id
45+
) AS contributionPercentageRunningTotal
46+
FROM collection_contributors_leaderboard
47+
) t
48+
cross join active_contributors
49+
WHERE
50+
contributionPercentageRunningTotal <= 51
51+
OR (contributionPercentageRunningTotal - contributionPercentage < 51)
52+
ORDER BY contributionPercentageRunningTotal ASC
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
DESCRIPTION >
2+
- `collection_contributors_leaderboard.pipe` serves the contributors leaderboard widget for a
3+
collection's Contributors tab, aggregated across every project in the collection.
4+
- Separate from `contributors_leaderboard.pipe` (the single-project version) because a naive
5+
collection-scoped port of that pipe's `ROUND(COUNT(af.id) * 100.0 / SUM(COUNT(af.id)) OVER (), 2)`
6+
contributionPercentage forces ClickHouse to fully materialize the per-member GROUP BY (potentially
7+
hundreds of thousands of distinct members at collection scale) before it can apply ORDER BY/LIMIT -
8+
confirmed via direct timing: the equivalent single-project pipe took 7-16s (once timing out
9+
entirely at Tinybird's 20s limit) on a 242-project collection, vs 2-3s for the count-only path
10+
with no window function. This pipe computes the total contribution count as a separate scalar
11+
subquery instead of a window function, avoiding that full-materialization cost.
12+
- Supports both count mode (`count=true`) and data mode for pagination-friendly leaderboard display.
13+
- Always shows `displayName` (not `publicName`) since a collection spans many projects with mixed
14+
LF/non-LF status - there's no single project-type answer to substitute on, unlike the single-project
15+
pipe's `is_request_from_non_lf_project` check.
16+
- Primary use case: powering the collection-scoped contributor ranking widget on Collection detail
17+
pages' Contributors tab.
18+
- Parameters:
19+
- `collectionSlug`: Required collection slug (e.g., 'cncf') - inherited from `segments_filtered_by_collection`.
20+
- `repos`: Optional array of repository URLs for filtering (inherited from `segments_filtered_by_collection`)
21+
- `startDate`: Optional DateTime filter for activities after timestamp (e.g., '2024-01-01 00:00:00')
22+
- `endDate`: Optional DateTime filter for activities before timestamp (e.g., '2024-12-31 23:59:59')
23+
- `platform`: Optional string filter for source platform (e.g., 'github', 'discord', 'slack')
24+
- `activity_type`: Optional string filter for single activity type (e.g., 'authored-commit')
25+
- `activity_types`: Optional array of activity types (e.g., ['authored-commit', 'co-authored-commit'])
26+
- `includeCollaborations`: Optional boolean, defaults to false - same toggle as the single-project pipe.
27+
- `count`: Optional boolean, when true returns contributor count instead of leaderboard data
28+
- `limit`: Optional integer for result pagination, defaults to 10
29+
- `offset`: Optional integer for result pagination, defaults to 0
30+
- Response:
31+
- Count mode (`count=true`): `count` (total number of contributors)
32+
- Data mode (default): `id`, `avatar`, `displayName`, `githubHandleArray`, `contributionCount`, `contributionPercentage`, `roles`
33+
34+
TAGS "Insights, Widget", "Collection", "Contributors"
35+
36+
NODE collection_contributors_total
37+
DESCRIPTION >
38+
Total contribution count across the collection - a plain aggregate with no GROUP BY, so it's
39+
cheap regardless of how many distinct members contributed. Computed once and reused as a scalar
40+
for the percentage calculation instead of a SUM(...) OVER () window function.
41+
42+
SQL >
43+
%
44+
SELECT count(af.id) AS total FROM activities_filtered af
45+
46+
NODE collection_member_aggregates
47+
DESCRIPTION >
48+
Per-member contribution counts, ranked and paginated. No window function here - the percentage
49+
is computed in the next node against the scalar total, so ORDER BY/LIMIT can apply directly
50+
after this GROUP BY without waiting on a second full-set pass.
51+
52+
SQL >
53+
%
54+
{% if Boolean(count, false) %} SELECT count(distinct af.memberId) FROM activities_filtered af
55+
{% else %}
56+
SELECT af.memberId, count(af.id) AS contributionCount
57+
FROM activities_filtered af
58+
GROUP BY af.memberId
59+
ORDER BY contributionCount DESC, af.memberId DESC
60+
LIMIT {{ Int32(limit, 10) }}
61+
OFFSET {{ Int32(offset, 0) }}
62+
{% end %}
63+
64+
NODE collection_contributors_leaderboard_result
65+
SQL >
66+
%
67+
{% if Boolean(count, false) %}
68+
SELECT count(distinct af.memberId) AS count FROM activities_filtered af
69+
{% else %}
70+
SELECT
71+
m.id,
72+
m.avatar,
73+
m.displayName,
74+
m.githubHandleArray,
75+
ma.contributionCount,
76+
ROUND(ma.contributionCount * 100.0 / total.total, 2) AS contributionPercentage,
77+
mr.roles
78+
FROM members_sorted AS m any
79+
INNER JOIN collection_member_aggregates ma ON ma.memberId = m.id
80+
LEFT JOIN member_roles mr ON mr.memberId = m.id
81+
CROSS JOIN collection_contributors_total total
82+
WHERE m.id IN (SELECT memberId FROM collection_member_aggregates)
83+
ORDER BY contributionCount DESC
84+
{% end %}

services/libs/tinybird/pipes/collection_insights_aggregate.pipe

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ DESCRIPTION >
99
- Response:
1010
- `projectCount`: count of distinct projects in the collection.
1111
- `uniqueContributorCount`: total unique contributors across all projects in the collection, deduplicated (a contributor active on multiple projects in the collection is counted once).
12-
- `avgHealthScore`: average of each project's health score in the collection, rounded.
12+
- `avgHealthScore`: average health score across the collection's onboarded projects, rounded. A project
13+
is "onboarded" when it has at least one contributor or organization attributed to it (same definition
14+
used by insightsProjects_filtered.pipe's `onboarded` filter and the frontend's project-row `isOnboarded`
15+
check) — projects with no attributed contributors/organizations have no meaningful health signal yet
16+
and would otherwise pull the average down artificially.
1317

1418
TAGS "Insights, Widget", "Collection"
1519

@@ -21,7 +25,7 @@ DESCRIPTION >
2125

2226
SQL >
2327
%
24-
SELECT id, segmentId, healthScore
28+
SELECT id, segmentId, healthScore, organizationCount, contributorCount
2529
FROM insights_projects_populated_ds
2630
WHERE
2731
enabled = 1
@@ -38,7 +42,11 @@ SQL >
3842
SELECT
3943
(SELECT count(distinct id) FROM collection_insights_aggregate_projects) AS projectCount,
4044
uniq(ar.memberId) AS uniqueContributorCount,
41-
(SELECT round(avg(healthScore)) FROM collection_insights_aggregate_projects) AS avgHealthScore
45+
(
46+
SELECT round(avg(healthScore))
47+
FROM collection_insights_aggregate_projects
48+
WHERE NOT (organizationCount = 0 AND contributorCount = 0)
49+
) AS avgHealthScore
4250
FROM activityRelations_deduplicated_cleaned_bucket_union AS ar
4351
WHERE
4452
ar.memberId != ''

0 commit comments

Comments
 (0)