From 7782241437fed0691a146aa7832b8abb60ae981f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Mon, 13 Jul 2026 12:43:55 +0100 Subject: [PATCH 1/9] feat: add collections v2 aggregate Tinybird pipes and toggle column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the showAggregateTabs toggle column on collections, and new Tinybird pipes aggregating project_insights and activity-relations data across an arbitrary list of projects for the LFX Insights Collections v2 UI (IN-1193, IN-1195). Signed-off-by: Gašper Grom --- ...wAggregateTabsColumnToCollectionsTable.sql | 3 ++ ...on_contributors_leaderboard_aggregate.pipe | 54 +++++++++++++++++++ .../collection_development_aggregate.pipe | 36 +++++++++++++ .../pipes/collection_insights_aggregate.pipe | 37 +++++++++++++ .../collection_popularity_aggregate.pipe | 42 +++++++++++++++ 5 files changed, 172 insertions(+) create mode 100644 backend/src/database/migrations/V1782500000__addShowAggregateTabsColumnToCollectionsTable.sql create mode 100644 services/libs/tinybird/pipes/collection_contributors_leaderboard_aggregate.pipe create mode 100644 services/libs/tinybird/pipes/collection_development_aggregate.pipe create mode 100644 services/libs/tinybird/pipes/collection_insights_aggregate.pipe create mode 100644 services/libs/tinybird/pipes/collection_popularity_aggregate.pipe diff --git a/backend/src/database/migrations/V1782500000__addShowAggregateTabsColumnToCollectionsTable.sql b/backend/src/database/migrations/V1782500000__addShowAggregateTabsColumnToCollectionsTable.sql new file mode 100644 index 0000000000..33936e905a --- /dev/null +++ b/backend/src/database/migrations/V1782500000__addShowAggregateTabsColumnToCollectionsTable.sql @@ -0,0 +1,3 @@ +-- Add the showAggregateTabs column to the collections table +ALTER TABLE collections + ADD COLUMN "showAggregateTabs" boolean DEFAULT true NOT NULL; diff --git a/services/libs/tinybird/pipes/collection_contributors_leaderboard_aggregate.pipe b/services/libs/tinybird/pipes/collection_contributors_leaderboard_aggregate.pipe new file mode 100644 index 0000000000..75c650046f --- /dev/null +++ b/services/libs/tinybird/pipes/collection_contributors_leaderboard_aggregate.pipe @@ -0,0 +1,54 @@ +DESCRIPTION > + - `collection_contributors_leaderboard_aggregate.pipe` serves the contributors leaderboard widget for a collection (an arbitrary list of projects). + - Returns the top 20 contributors ranked by total contribution count, aggregated/deduplicated across all projects in the list (a contributor active on multiple projects is summed once, not listed per project). + - Parameters: + - `ids`: Required array of project ids (as returned by `project_insights.pipe`'s `id`/`ids`) to aggregate over. + - Response: + - `id`: member id. + - `avatar`: member avatar URL. + - `displayName`: member display name. + - `githubHandleArray`: member's GitHub usernames. + - `contributionCount`: total contribution count for the member, summed across all matched projects. + +TAGS "Insights, Widget", "Collection" + +NODE collection_contributors_leaderboard_aggregate_projects +DESCRIPTION > + Resolves the input project ids to their segment ids, since activity data is keyed by segmentId, not project id + +SQL > + % + SELECT id, segmentId + FROM project_insights_copy_ds + WHERE + type = 'project' + AND id IN {{ Array(ids, 'String', description="Filter by project id list", required=True) }} + +NODE collection_contributors_leaderboard_aggregate_members +DESCRIPTION > + Sums contribution counts per member across the matched projects' segments + +SQL > + SELECT ar.memberId AS memberId, count(ar.activityId) AS contributionCount + FROM activityRelations_deduplicated_cleaned_bucket_union AS ar + WHERE + ar.memberId != '' + AND ar.segmentId IN (SELECT segmentId FROM collection_contributors_leaderboard_aggregate_projects) + GROUP BY ar.memberId + ORDER BY contributionCount DESC, memberId DESC + LIMIT 20 + +NODE collection_contributors_leaderboard_aggregate_results +DESCRIPTION > + Joins the top members with their profile data + +SQL > + SELECT + m.id AS id, + m.avatar AS avatar, + m.displayName AS displayName, + m.githubHandleArray AS githubHandleArray, + ma.contributionCount AS contributionCount + FROM collection_contributors_leaderboard_aggregate_members AS ma + INNER JOIN members_sorted AS m ON m.id = ma.memberId + ORDER BY contributionCount DESC, id DESC diff --git a/services/libs/tinybird/pipes/collection_development_aggregate.pipe b/services/libs/tinybird/pipes/collection_development_aggregate.pipe new file mode 100644 index 0000000000..92de31979a --- /dev/null +++ b/services/libs/tinybird/pipes/collection_development_aggregate.pipe @@ -0,0 +1,36 @@ +DESCRIPTION > + - `collection_development_aggregate.pipe` serves aggregate development activity metrics for a collection (an arbitrary list of projects). + - Returns a single row with active contributors and active organizations aggregated across all projects in the list, deduplicated (a contributor or organization active on multiple projects in the list is counted once, not once per project), restricted to the last 365 days to mirror the per-project `activeContributorsLast365Days` / `activeOrganizationsLast365Days` semantics. + - Parameters: + - `ids`: Required array of project ids (as returned by `project_insights.pipe`'s `id`/`ids`) to aggregate over. + - Response: + - `activeContributorsLast365Days`: total unique contributors across all matched projects' segments in the last 365 days. + - `activeOrganizationsLast365Days`: total unique organizations across all matched projects' segments in the last 365 days. + +TAGS "Insights, Widget", "Collection" + +NODE collection_development_aggregate_projects +DESCRIPTION > + Resolves the input project ids to their segment ids, since activity data is keyed by segmentId, not project id + +SQL > + % + SELECT id, segmentId + FROM project_insights_copy_ds + WHERE + type = 'project' + AND id IN {{ Array(ids, 'String', description="Filter by project id list", required=True) }} + +NODE collection_development_aggregate_results +DESCRIPTION > + Counts unique contributors and unique organizations across matched projects' segments in the last 365 days + +SQL > + SELECT + uniq(case when ar.memberId != '' then ar.memberId else null end) AS activeContributorsLast365Days, + uniq(case when ar.organizationId != '' then ar.organizationId else null end) AS activeOrganizationsLast365Days + FROM activityRelations_deduplicated_cleaned_bucket_union AS ar + WHERE + ar.segmentId IN (SELECT segmentId FROM collection_development_aggregate_projects) + AND ar.timestamp >= now() - INTERVAL 365 DAY + AND ar.timestamp < now() diff --git a/services/libs/tinybird/pipes/collection_insights_aggregate.pipe b/services/libs/tinybird/pipes/collection_insights_aggregate.pipe new file mode 100644 index 0000000000..858969d0e9 --- /dev/null +++ b/services/libs/tinybird/pipes/collection_insights_aggregate.pipe @@ -0,0 +1,37 @@ +DESCRIPTION > + - `collection_insights_aggregate.pipe` serves aggregate insights metrics for a collection (an arbitrary list of projects). + - Returns a single row with metrics aggregated across all projects in the list, not one row per project. + - Parameters: + - `ids`: Required array of project ids (as returned by `project_insights.pipe`'s `id`/`ids`) to aggregate over. + - Response: + - `projectCount`: count of distinct project ids from the input list that matched an existing project. + - `uniqueContributorCount`: total unique contributors across all matched projects, deduplicated (a contributor active on multiple projects in the list is counted once). + - `avgHealthScore`: average of each matched project's health score, rounded. + +TAGS "Insights, Widget", "Collection" + +NODE collection_insights_aggregate_projects +DESCRIPTION > + Resolves the input project ids to their segment ids, since activity data is keyed by segmentId, not project id + +SQL > + % + SELECT id, segmentId, healthScore + FROM project_insights_copy_ds + WHERE + type = 'project' + AND id IN {{ Array(ids, 'String', description="Filter by project id list", required=True) }} + +NODE collection_insights_aggregate_results +DESCRIPTION > + Aggregates health score across matched projects and counts unique contributors across their segments in one pass + +SQL > + SELECT + (SELECT count(distinct id) FROM collection_insights_aggregate_projects) AS projectCount, + uniq(ar.memberId) AS uniqueContributorCount, + (SELECT round(avg(healthScore)) FROM collection_insights_aggregate_projects) AS avgHealthScore + FROM activityRelations_deduplicated_cleaned_bucket_union AS ar + WHERE + ar.memberId != '' + AND ar.segmentId IN (SELECT segmentId FROM collection_insights_aggregate_projects) diff --git a/services/libs/tinybird/pipes/collection_popularity_aggregate.pipe b/services/libs/tinybird/pipes/collection_popularity_aggregate.pipe new file mode 100644 index 0000000000..a630f2c44d --- /dev/null +++ b/services/libs/tinybird/pipes/collection_popularity_aggregate.pipe @@ -0,0 +1,42 @@ +DESCRIPTION > + - `collection_popularity_aggregate.pipe` serves aggregate popularity metrics for a collection (an arbitrary list of projects). + - Returns a single row with stars and forks summed across all projects in the list, for both the current and previous 365-day windows, so the frontend can compute a trend delta. + - Stars and forks are not deduplication-sensitive (a star on one repo is independent of a star on another), so a plain SUM across matched projects is correct. + - Parameters: + - `ids`: Required array of project ids (as returned by `project_insights.pipe`'s `id`/`ids`) to aggregate over. + - Response: + - `totalStars`: sum of `starsLast365Days` across all matched projects. + - `totalForks`: sum of `forksLast365Days` across all matched projects. + - `starsPrevious365Days`: sum of `starsPrevious365Days` across all matched projects. + - `forksPrevious365Days`: sum of `forksPrevious365Days` across all matched projects. + +TAGS "Insights, Widget", "Collection" + +NODE collection_popularity_aggregate_projects +DESCRIPTION > + Resolves the input project ids to their per-project popularity fields + +SQL > + % + SELECT + id, + starsLast365Days, + forksLast365Days, + starsPrevious365Days, + forksPrevious365Days + FROM project_insights_copy_ds + WHERE + type = 'project' + AND id IN {{ Array(ids, 'String', description="Filter by project id list", required=True) }} + +NODE collection_popularity_aggregate_results +DESCRIPTION > + Sums stars and forks across matched projects for the current and previous 365-day windows + +SQL > + SELECT + sum(starsLast365Days) AS totalStars, + sum(forksLast365Days) AS totalForks, + sum(starsPrevious365Days) AS starsPrevious365Days, + sum(forksPrevious365Days) AS forksPrevious365Days + FROM collection_popularity_aggregate_projects From 569d53b8932c057bfa9bdfd723341d1bc99dc0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 14 Jul 2026 10:38:34 +0100 Subject: [PATCH 2/9] fix: resolve collection_insights_aggregate via collectionsSlugs, not project ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Takes collectionSlug directly instead of a caller-supplied project-id array, resolving collection membership via insights_projects_populated_ds.collectionsSlugs (has() filter, same pattern as insightsProjects_filtered.pipe). - Fixes segmentId resolution: project_insights_copy_ds has no segmentId column at all (confirmed by the Tinybird deploy error); insights_projects_populated_ds has both segmentId and healthScore. Verified live against Tinybird - returns correct projectCount, uniqueContributorCount, and avgHealthScore for a real collection. Signed-off-by: Gašper Grom --- .../pipes/collection_insights_aggregate.pipe | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/services/libs/tinybird/pipes/collection_insights_aggregate.pipe b/services/libs/tinybird/pipes/collection_insights_aggregate.pipe index 858969d0e9..df178e8822 100644 --- a/services/libs/tinybird/pipes/collection_insights_aggregate.pipe +++ b/services/libs/tinybird/pipes/collection_insights_aggregate.pipe @@ -1,26 +1,34 @@ DESCRIPTION > - - `collection_insights_aggregate.pipe` serves aggregate insights metrics for a collection (an arbitrary list of projects). - - Returns a single row with metrics aggregated across all projects in the list, not one row per project. + - `collection_insights_aggregate.pipe` serves aggregate insights metrics for a collection. + - Returns a single row with metrics aggregated across every project in the collection, not one row per project. - Parameters: - - `ids`: Required array of project ids (as returned by `project_insights.pipe`'s `id`/`ids`) to aggregate over. + - `collectionSlug`: Required collection slug. insights_projects_populated_ds.collectionsSlugs already lists + every collection each project belongs to (see insightsProjects_filtered.pipe for the same filter pattern), + so this pipe resolves collection membership directly rather than requiring the caller to first look up a + project id list. - Response: - - `projectCount`: count of distinct project ids from the input list that matched an existing project. - - `uniqueContributorCount`: total unique contributors across all matched projects, deduplicated (a contributor active on multiple projects in the list is counted once). - - `avgHealthScore`: average of each matched project's health score, rounded. + - `projectCount`: count of distinct projects in the collection. + - `uniqueContributorCount`: total unique contributors across all projects in the collection, deduplicated (a contributor active on multiple projects in the collection is counted once). + - `avgHealthScore`: average of each project's health score in the collection, rounded. TAGS "Insights, Widget", "Collection" NODE collection_insights_aggregate_projects DESCRIPTION > - Resolves the input project ids to their segment ids, since activity data is keyed by segmentId, not project id + Resolves the collection's member projects to their segment ids and health scores directly via + collectionsSlugs, matching the same has(collectionsSlugs, ...) filter already used by + insightsProjects_filtered.pipe. SQL > % SELECT id, segmentId, healthScore - FROM project_insights_copy_ds + FROM insights_projects_populated_ds WHERE - type = 'project' - AND id IN {{ Array(ids, 'String', description="Filter by project id list", required=True) }} + enabled = 1 + AND has( + collectionsSlugs, + {{ String(collectionSlug, description="Filter by collection slug", required=True) }} + ) NODE collection_insights_aggregate_results DESCRIPTION > From 2fdaf56a307a5419e30eab2eef0e061adc8f671a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 14 Jul 2026 10:39:03 +0100 Subject: [PATCH 3/9] chore: remove unused IN-1195 aggregate pipes for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contributors/Popularity/Development collection tabs are hidden in the insights UI while IN-1195 is being reworked - removing the corresponding Tinybird pipes so the PR doesn't ship dead backend code. The showAggregateTabs migration and collection_insights_aggregate.pipe (IN-1193's header metrics, already deployed and working) stay. Signed-off-by: Gašper Grom --- ...on_contributors_leaderboard_aggregate.pipe | 54 ------------------- .../collection_development_aggregate.pipe | 36 ------------- .../collection_popularity_aggregate.pipe | 42 --------------- 3 files changed, 132 deletions(-) delete mode 100644 services/libs/tinybird/pipes/collection_contributors_leaderboard_aggregate.pipe delete mode 100644 services/libs/tinybird/pipes/collection_development_aggregate.pipe delete mode 100644 services/libs/tinybird/pipes/collection_popularity_aggregate.pipe diff --git a/services/libs/tinybird/pipes/collection_contributors_leaderboard_aggregate.pipe b/services/libs/tinybird/pipes/collection_contributors_leaderboard_aggregate.pipe deleted file mode 100644 index 75c650046f..0000000000 --- a/services/libs/tinybird/pipes/collection_contributors_leaderboard_aggregate.pipe +++ /dev/null @@ -1,54 +0,0 @@ -DESCRIPTION > - - `collection_contributors_leaderboard_aggregate.pipe` serves the contributors leaderboard widget for a collection (an arbitrary list of projects). - - Returns the top 20 contributors ranked by total contribution count, aggregated/deduplicated across all projects in the list (a contributor active on multiple projects is summed once, not listed per project). - - Parameters: - - `ids`: Required array of project ids (as returned by `project_insights.pipe`'s `id`/`ids`) to aggregate over. - - Response: - - `id`: member id. - - `avatar`: member avatar URL. - - `displayName`: member display name. - - `githubHandleArray`: member's GitHub usernames. - - `contributionCount`: total contribution count for the member, summed across all matched projects. - -TAGS "Insights, Widget", "Collection" - -NODE collection_contributors_leaderboard_aggregate_projects -DESCRIPTION > - Resolves the input project ids to their segment ids, since activity data is keyed by segmentId, not project id - -SQL > - % - SELECT id, segmentId - FROM project_insights_copy_ds - WHERE - type = 'project' - AND id IN {{ Array(ids, 'String', description="Filter by project id list", required=True) }} - -NODE collection_contributors_leaderboard_aggregate_members -DESCRIPTION > - Sums contribution counts per member across the matched projects' segments - -SQL > - SELECT ar.memberId AS memberId, count(ar.activityId) AS contributionCount - FROM activityRelations_deduplicated_cleaned_bucket_union AS ar - WHERE - ar.memberId != '' - AND ar.segmentId IN (SELECT segmentId FROM collection_contributors_leaderboard_aggregate_projects) - GROUP BY ar.memberId - ORDER BY contributionCount DESC, memberId DESC - LIMIT 20 - -NODE collection_contributors_leaderboard_aggregate_results -DESCRIPTION > - Joins the top members with their profile data - -SQL > - SELECT - m.id AS id, - m.avatar AS avatar, - m.displayName AS displayName, - m.githubHandleArray AS githubHandleArray, - ma.contributionCount AS contributionCount - FROM collection_contributors_leaderboard_aggregate_members AS ma - INNER JOIN members_sorted AS m ON m.id = ma.memberId - ORDER BY contributionCount DESC, id DESC diff --git a/services/libs/tinybird/pipes/collection_development_aggregate.pipe b/services/libs/tinybird/pipes/collection_development_aggregate.pipe deleted file mode 100644 index 92de31979a..0000000000 --- a/services/libs/tinybird/pipes/collection_development_aggregate.pipe +++ /dev/null @@ -1,36 +0,0 @@ -DESCRIPTION > - - `collection_development_aggregate.pipe` serves aggregate development activity metrics for a collection (an arbitrary list of projects). - - Returns a single row with active contributors and active organizations aggregated across all projects in the list, deduplicated (a contributor or organization active on multiple projects in the list is counted once, not once per project), restricted to the last 365 days to mirror the per-project `activeContributorsLast365Days` / `activeOrganizationsLast365Days` semantics. - - Parameters: - - `ids`: Required array of project ids (as returned by `project_insights.pipe`'s `id`/`ids`) to aggregate over. - - Response: - - `activeContributorsLast365Days`: total unique contributors across all matched projects' segments in the last 365 days. - - `activeOrganizationsLast365Days`: total unique organizations across all matched projects' segments in the last 365 days. - -TAGS "Insights, Widget", "Collection" - -NODE collection_development_aggregate_projects -DESCRIPTION > - Resolves the input project ids to their segment ids, since activity data is keyed by segmentId, not project id - -SQL > - % - SELECT id, segmentId - FROM project_insights_copy_ds - WHERE - type = 'project' - AND id IN {{ Array(ids, 'String', description="Filter by project id list", required=True) }} - -NODE collection_development_aggregate_results -DESCRIPTION > - Counts unique contributors and unique organizations across matched projects' segments in the last 365 days - -SQL > - SELECT - uniq(case when ar.memberId != '' then ar.memberId else null end) AS activeContributorsLast365Days, - uniq(case when ar.organizationId != '' then ar.organizationId else null end) AS activeOrganizationsLast365Days - FROM activityRelations_deduplicated_cleaned_bucket_union AS ar - WHERE - ar.segmentId IN (SELECT segmentId FROM collection_development_aggregate_projects) - AND ar.timestamp >= now() - INTERVAL 365 DAY - AND ar.timestamp < now() diff --git a/services/libs/tinybird/pipes/collection_popularity_aggregate.pipe b/services/libs/tinybird/pipes/collection_popularity_aggregate.pipe deleted file mode 100644 index a630f2c44d..0000000000 --- a/services/libs/tinybird/pipes/collection_popularity_aggregate.pipe +++ /dev/null @@ -1,42 +0,0 @@ -DESCRIPTION > - - `collection_popularity_aggregate.pipe` serves aggregate popularity metrics for a collection (an arbitrary list of projects). - - Returns a single row with stars and forks summed across all projects in the list, for both the current and previous 365-day windows, so the frontend can compute a trend delta. - - Stars and forks are not deduplication-sensitive (a star on one repo is independent of a star on another), so a plain SUM across matched projects is correct. - - Parameters: - - `ids`: Required array of project ids (as returned by `project_insights.pipe`'s `id`/`ids`) to aggregate over. - - Response: - - `totalStars`: sum of `starsLast365Days` across all matched projects. - - `totalForks`: sum of `forksLast365Days` across all matched projects. - - `starsPrevious365Days`: sum of `starsPrevious365Days` across all matched projects. - - `forksPrevious365Days`: sum of `forksPrevious365Days` across all matched projects. - -TAGS "Insights, Widget", "Collection" - -NODE collection_popularity_aggregate_projects -DESCRIPTION > - Resolves the input project ids to their per-project popularity fields - -SQL > - % - SELECT - id, - starsLast365Days, - forksLast365Days, - starsPrevious365Days, - forksPrevious365Days - FROM project_insights_copy_ds - WHERE - type = 'project' - AND id IN {{ Array(ids, 'String', description="Filter by project id list", required=True) }} - -NODE collection_popularity_aggregate_results -DESCRIPTION > - Sums stars and forks across matched projects for the current and previous 365-day windows - -SQL > - SELECT - sum(starsLast365Days) AS totalStars, - sum(forksLast365Days) AS totalForks, - sum(starsPrevious365Days) AS starsPrevious365Days, - sum(forksPrevious365Days) AS forksPrevious365Days - FROM collection_popularity_aggregate_projects From 8d40eb146f1883ab3650fdbfa350cef082221356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 14 Jul 2026 12:06:50 +0100 Subject: [PATCH 4/9] feat: manage showAggregateTabs from CDP admin, default false + backfill curated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replaces the earlier hand-written migration with one scaffolded via ./scripts/cli scaffold create-migration, per repo convention. - showAggregateTabs now defaults to false for all collections, then backfills to true for curated (LF Foundation) collections only (ssoUserId IS NULL) - the tab-gating logic lives entirely in this column now, not split between it and a CURATED type check on the insights frontend. - Adds an editable "Show aggregate tabs" toggle to the CDP admin collection add/edit form, defaulting to true for new collections (admin-created collections are always curated), following the exact same DAL/model/form pattern already used for the sibling starred field. Signed-off-by: Gašper Grom --- ...addShowAggregateTabsColumnToCollectionsTable.sql | 3 --- ...addShowAggregateTabsColumnToCollectionsTable.sql | 10 ++++++++++ .../collections/components/lf-collection-add.vue | 13 +++++++++++++ .../modules/collections/models/collection.model.ts | 3 +++ .../libs/data-access-layer/src/collections/index.ts | 7 +++++-- 5 files changed, 31 insertions(+), 5 deletions(-) delete mode 100644 backend/src/database/migrations/V1782500000__addShowAggregateTabsColumnToCollectionsTable.sql create mode 100644 backend/src/database/migrations/V1784026542__addShowAggregateTabsColumnToCollectionsTable.sql diff --git a/backend/src/database/migrations/V1782500000__addShowAggregateTabsColumnToCollectionsTable.sql b/backend/src/database/migrations/V1782500000__addShowAggregateTabsColumnToCollectionsTable.sql deleted file mode 100644 index 33936e905a..0000000000 --- a/backend/src/database/migrations/V1782500000__addShowAggregateTabsColumnToCollectionsTable.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Add the showAggregateTabs column to the collections table -ALTER TABLE collections - ADD COLUMN "showAggregateTabs" boolean DEFAULT true NOT NULL; diff --git a/backend/src/database/migrations/V1784026542__addShowAggregateTabsColumnToCollectionsTable.sql b/backend/src/database/migrations/V1784026542__addShowAggregateTabsColumnToCollectionsTable.sql new file mode 100644 index 0000000000..0ad3c775d6 --- /dev/null +++ b/backend/src/database/migrations/V1784026542__addShowAggregateTabsColumnToCollectionsTable.sql @@ -0,0 +1,10 @@ +-- Add the showAggregateTabs column to the collections table, defaulting to false. +ALTER TABLE collections + ADD COLUMN "showAggregateTabs" boolean DEFAULT false NOT NULL; + +-- Only LF Foundation (curated) collections get the in-depth aggregate tabs by default. +-- ssoUserId is only set for community/user-curated collections (see V1772438175), so +-- ssoUserId IS NULL identifies curated collections. +UPDATE collections +SET "showAggregateTabs" = true +WHERE "ssoUserId" IS NULL; diff --git a/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue b/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue index ff78cb025d..8a09957977 100644 --- a/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue +++ b/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue @@ -186,6 +186,16 @@ + + +
+ +
+ + Show in-depth aggregate metric tabs on the public collection page +
+
+
({ color: '', projects: [], starred: false, + showAggregateTabs: true, }); const rules = { @@ -327,6 +339,7 @@ const onSubmit = () => { starred: project?.starred || false, })), starred: !!form.starred, + showAggregateTabs: !!form.showAggregateTabs, categoryId: form.categoryId, slug: form.name.toLowerCase().replace(/ /g, '-'), }; diff --git a/frontend/src/modules/admin/modules/collections/models/collection.model.ts b/frontend/src/modules/admin/modules/collections/models/collection.model.ts index cee9b61151..71ee6bb73a 100644 --- a/frontend/src/modules/admin/modules/collections/models/collection.model.ts +++ b/frontend/src/modules/admin/modules/collections/models/collection.model.ts @@ -14,6 +14,7 @@ export interface CollectionModel { projects: InsightsProjectModel[]; category: Category & {categoryGroupType: string, categoryGroupName: string}; starred?: boolean; + showAggregateTabs?: boolean; } export interface CollectionRequest { @@ -25,6 +26,7 @@ export interface CollectionRequest { color?: string; slug: string; starred: boolean; + showAggregateTabs: boolean; projects: { id: string; starred: boolean; @@ -40,4 +42,5 @@ export interface CollectionFormModel { color: string; projects: InsightsProjectModel[]; starred: boolean; + showAggregateTabs: boolean; } diff --git a/services/libs/data-access-layer/src/collections/index.ts b/services/libs/data-access-layer/src/collections/index.ts index be5bc75aa9..ca9852c51c 100644 --- a/services/libs/data-access-layer/src/collections/index.ts +++ b/services/libs/data-access-layer/src/collections/index.ts @@ -21,6 +21,7 @@ export interface ICreateCollection { name: string slug?: string starred: boolean + showAggregateTabs?: boolean isPrivate?: boolean ssoUserId?: string | null logoUrl?: string | null @@ -93,6 +94,7 @@ export enum CollectionField { SLUG = 'slug', SSO_USER_ID = 'ssoUserId', STARRED = 'starred', + SHOW_AGGREGATE_TABS = 'showAggregateTabs', UPDATED_AT = 'updatedAt', DELETED_AT = 'deletedAt', } @@ -144,12 +146,13 @@ export async function createCollection( logoUrl: null, imageUrl: null, color: null, + showAggregateTabs: true, ...collection, } return qx.selectOne( ` - INSERT INTO collections (name, description, slug, "categoryId", starred, "logoUrl", "imageUrl", color) - VALUES ($(name), $(description), $(slug), $(categoryId), $(starred), $(logoUrl), $(imageUrl), $(color)) + INSERT INTO collections (name, description, slug, "categoryId", starred, "logoUrl", "imageUrl", color, "showAggregateTabs") + VALUES ($(name), $(description), $(slug), $(categoryId), $(starred), $(logoUrl), $(imageUrl), $(color), $(showAggregateTabs)) RETURNING * `, data, From 313b86b2822f6ea71661553921de07b2badfccfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 14 Jul 2026 13:22:09 +0100 Subject: [PATCH 5/9] chore: exclude packages_worker family from clean-start-fe-dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend dev only needs api running; the packages_worker workers (npm/pypi/maven/osv/dockerhub/cargo/go/nuget/rubygems/security-contacts, bq-dataset-ingest, github-repos-enricher) plus automatic-projects-discovery-worker, pcc-sync-worker, and projects-evaluation-worker were being built/started unnecessarily on every clean-start-fe-dev / service-restart-fe-dev. Claude-Session: https://claude.ai/code/session_01J17dWfDoruiB5xn5iNM89V Signed-off-by: Gašper Grom --- scripts/cli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/cli b/scripts/cli index 117f1b371d..7bdfd7e3be 100755 --- a/scripts/cli +++ b/scripts/cli @@ -1182,14 +1182,14 @@ while test $# -gt 0; do exit ;; service-restart-fe-dev) - IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker") + IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker" "automatic-projects-discovery-worker" "pcc-sync-worker" "projects-evaluation-worker" "bq-dataset-ingest" "cargo-worker" "dockerhub-sync" "github-repos-enricher" "go-worker" "maven-worker" "npm-worker" "nuget-worker" "osv-worker" "pypi-worker" "rubygems-worker" "security-contacts-worker") DEV=1 kill_all_containers service_start exit ;; clean-start-fe-dev) - IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker" "cache-worker" "categorization-worker" "cron-service" "data-sink-worker" "git-integration" "integration-run-worker" "integration-stream-worker" "nango-webhook-api" "nango-worker" "script-executor-worker" "search-sync-api" "search-sync-worker" "security-best-practices-worker" "snowflake-connectors-worker") + IGNORED_SERVICES=("frontend" "python-worker" "job-generator" "webhook-api" "profiles-worker" "organizations-enrichment-worker" "merge-suggestions-worker" "members-enrichment-worker" "exports-worker" "entity-merging-worker" "cache-worker" "categorization-worker" "cron-service" "data-sink-worker" "git-integration" "integration-run-worker" "integration-stream-worker" "nango-webhook-api" "nango-worker" "script-executor-worker" "search-sync-api" "search-sync-worker" "security-best-practices-worker" "snowflake-connectors-worker" "automatic-projects-discovery-worker" "pcc-sync-worker" "projects-evaluation-worker" "bq-dataset-ingest" "cargo-worker" "dockerhub-sync" "github-repos-enricher" "go-worker" "maven-worker" "npm-worker" "nuget-worker" "osv-worker" "pypi-worker" "rubygems-worker" "security-contacts-worker") CLEAN_START=1 DEV=1 start From bd5c2b1c87079aa38df6ede92e04d35bd458f32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 14 Jul 2026 13:23:52 +0100 Subject: [PATCH 6/9] chore: add undo migration for showAggregateTabs column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pairs with V1784026542 - DROP COLUMN reverses the ADD COLUMN + backfill, following this repo's occasional U undo-migration convention. Signed-off-by: Gašper Grom --- ...784026542__addShowAggregateTabsColumnToCollectionsTable.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backend/src/database/migrations/U1784026542__addShowAggregateTabsColumnToCollectionsTable.sql diff --git a/backend/src/database/migrations/U1784026542__addShowAggregateTabsColumnToCollectionsTable.sql b/backend/src/database/migrations/U1784026542__addShowAggregateTabsColumnToCollectionsTable.sql new file mode 100644 index 0000000000..2e04b8c152 --- /dev/null +++ b/backend/src/database/migrations/U1784026542__addShowAggregateTabsColumnToCollectionsTable.sql @@ -0,0 +1,3 @@ +-- Reverses V1784026542: drops the showAggregateTabs column added to collections. +ALTER TABLE collections + DROP COLUMN "showAggregateTabs"; From d75a2496f656b3dbfb338846d318bb58fa1eebcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 14 Jul 2026 13:42:33 +0100 Subject: [PATCH 7/9] fix: address PR #4336 review comments (CM-1193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Filter uniqueContributorCount in collection_insights_aggregate.pipe to code contributions/collaborations only, matching the existing segmentId_aggregates_snapshot.pipe convention (was inflated by stars, joins, and other non-contribution activity types). - Default showAggregateTabs based on collection type in createCollection (community collections with ssoUserId now default to false, matching the migration's backfill policy, instead of always defaulting true). - Fix accessible name association for the "Show aggregate tabs" switch by moving its description text into LfSwitch's default slot instead of a sibling span outside the label. Signed-off-by: Gašper Grom --- .../collections/components/lf-collection-add.vue | 5 +++-- .../libs/data-access-layer/src/collections/index.ts | 5 ++++- .../tinybird/pipes/collection_insights_aggregate.pipe | 11 ++++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue b/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue index 8a09957977..ca7a2573c0 100644 --- a/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue +++ b/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue @@ -191,8 +191,9 @@
- - Show in-depth aggregate metric tabs on the public collection page + + Show in-depth aggregate metric tabs on the public collection page +
diff --git a/services/libs/data-access-layer/src/collections/index.ts b/services/libs/data-access-layer/src/collections/index.ts index ca9852c51c..297a18ab49 100644 --- a/services/libs/data-access-layer/src/collections/index.ts +++ b/services/libs/data-access-layer/src/collections/index.ts @@ -146,7 +146,10 @@ export async function createCollection( logoUrl: null, imageUrl: null, color: null, - showAggregateTabs: true, + // Only LF Foundation (curated) collections get aggregate tabs by default. + // ssoUserId is only set for community/user-curated collections, so + // ssoUserId being unset identifies curated collections (see V1784026542). + showAggregateTabs: !collection.ssoUserId, ...collection, } return qx.selectOne( diff --git a/services/libs/tinybird/pipes/collection_insights_aggregate.pipe b/services/libs/tinybird/pipes/collection_insights_aggregate.pipe index df178e8822..15a5c1f2c6 100644 --- a/services/libs/tinybird/pipes/collection_insights_aggregate.pipe +++ b/services/libs/tinybird/pipes/collection_insights_aggregate.pipe @@ -3,9 +3,9 @@ DESCRIPTION > - Returns a single row with metrics aggregated across every project in the collection, not one row per project. - Parameters: - `collectionSlug`: Required collection slug. insights_projects_populated_ds.collectionsSlugs already lists - every collection each project belongs to (see insightsProjects_filtered.pipe for the same filter pattern), - so this pipe resolves collection membership directly rather than requiring the caller to first look up a - project id list. + every collection each project belongs to (see insightsProjects_filtered.pipe for the same filter pattern), + so this pipe resolves collection membership directly rather than requiring the caller to first look up a + project id list. - Response: - `projectCount`: count of distinct projects in the collection. - `uniqueContributorCount`: total unique contributors across all projects in the collection, deduplicated (a contributor active on multiple projects in the collection is counted once). @@ -43,3 +43,8 @@ SQL > WHERE ar.memberId != '' AND ar.segmentId IN (SELECT segmentId FROM collection_insights_aggregate_projects) + AND (ar.type, ar.platform) IN ( + SELECT activityType, platform + FROM activityTypes + WHERE isCodeContribution = 1 OR isCollaboration = 1 + ) From 64db61790ce04f0ddf48e2885bcbbd9ebe932abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 14 Jul 2026 13:45:40 +0100 Subject: [PATCH 8/9] fix: make CollectionRequest.showAggregateTabs optional (CM-1193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit collection.page.vue's featured-star toggle builds a CollectionRequest object literal that omits showAggregateTabs (and other optional fields), relying on the backend to preserve omitted values on partial update. The field was typed as required, which breaks that existing call site. Signed-off-by: Gašper Grom --- .../admin/modules/collections/models/collection.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/modules/admin/modules/collections/models/collection.model.ts b/frontend/src/modules/admin/modules/collections/models/collection.model.ts index 71ee6bb73a..abd695169f 100644 --- a/frontend/src/modules/admin/modules/collections/models/collection.model.ts +++ b/frontend/src/modules/admin/modules/collections/models/collection.model.ts @@ -26,7 +26,7 @@ export interface CollectionRequest { color?: string; slug: string; starred: boolean; - showAggregateTabs: boolean; + showAggregateTabs?: boolean; projects: { id: string; starred: boolean; From cc3b75964d6fe26c9a556e745872210e465a9caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 14 Jul 2026 14:13:41 +0100 Subject: [PATCH 9/9] revert: drop showAggregateTabs column, migration, and CDP admin management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per product decision: which collections show the in-depth aggregate tabs doesn't need to be independently manageable per foundation - the insights frontend can derive this directly from collection type (curated vs community), which it already knows. Removes the migration, the DAL/admin UI wiring, and the per-foundation CDP toggle entirely. Signed-off-by: Gašper Grom --- ...ddShowAggregateTabsColumnToCollectionsTable.sql | 3 --- ...ddShowAggregateTabsColumnToCollectionsTable.sql | 10 ---------- .../collections/components/lf-collection-add.vue | 14 -------------- .../modules/collections/models/collection.model.ts | 3 --- .../data-access-layer/src/collections/index.ts | 10 ++-------- 5 files changed, 2 insertions(+), 38 deletions(-) delete mode 100644 backend/src/database/migrations/U1784026542__addShowAggregateTabsColumnToCollectionsTable.sql delete mode 100644 backend/src/database/migrations/V1784026542__addShowAggregateTabsColumnToCollectionsTable.sql diff --git a/backend/src/database/migrations/U1784026542__addShowAggregateTabsColumnToCollectionsTable.sql b/backend/src/database/migrations/U1784026542__addShowAggregateTabsColumnToCollectionsTable.sql deleted file mode 100644 index 2e04b8c152..0000000000 --- a/backend/src/database/migrations/U1784026542__addShowAggregateTabsColumnToCollectionsTable.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Reverses V1784026542: drops the showAggregateTabs column added to collections. -ALTER TABLE collections - DROP COLUMN "showAggregateTabs"; diff --git a/backend/src/database/migrations/V1784026542__addShowAggregateTabsColumnToCollectionsTable.sql b/backend/src/database/migrations/V1784026542__addShowAggregateTabsColumnToCollectionsTable.sql deleted file mode 100644 index 0ad3c775d6..0000000000 --- a/backend/src/database/migrations/V1784026542__addShowAggregateTabsColumnToCollectionsTable.sql +++ /dev/null @@ -1,10 +0,0 @@ --- Add the showAggregateTabs column to the collections table, defaulting to false. -ALTER TABLE collections - ADD COLUMN "showAggregateTabs" boolean DEFAULT false NOT NULL; - --- Only LF Foundation (curated) collections get the in-depth aggregate tabs by default. --- ssoUserId is only set for community/user-curated collections (see V1772438175), so --- ssoUserId IS NULL identifies curated collections. -UPDATE collections -SET "showAggregateTabs" = true -WHERE "ssoUserId" IS NULL; diff --git a/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue b/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue index ca7a2573c0..ff78cb025d 100644 --- a/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue +++ b/frontend/src/modules/admin/modules/collections/components/lf-collection-add.vue @@ -186,17 +186,6 @@ - - -
- -
- - Show in-depth aggregate metric tabs on the public collection page - -
-
-
({ color: '', projects: [], starred: false, - showAggregateTabs: true, }); const rules = { @@ -340,7 +327,6 @@ const onSubmit = () => { starred: project?.starred || false, })), starred: !!form.starred, - showAggregateTabs: !!form.showAggregateTabs, categoryId: form.categoryId, slug: form.name.toLowerCase().replace(/ /g, '-'), }; diff --git a/frontend/src/modules/admin/modules/collections/models/collection.model.ts b/frontend/src/modules/admin/modules/collections/models/collection.model.ts index abd695169f..cee9b61151 100644 --- a/frontend/src/modules/admin/modules/collections/models/collection.model.ts +++ b/frontend/src/modules/admin/modules/collections/models/collection.model.ts @@ -14,7 +14,6 @@ export interface CollectionModel { projects: InsightsProjectModel[]; category: Category & {categoryGroupType: string, categoryGroupName: string}; starred?: boolean; - showAggregateTabs?: boolean; } export interface CollectionRequest { @@ -26,7 +25,6 @@ export interface CollectionRequest { color?: string; slug: string; starred: boolean; - showAggregateTabs?: boolean; projects: { id: string; starred: boolean; @@ -42,5 +40,4 @@ export interface CollectionFormModel { color: string; projects: InsightsProjectModel[]; starred: boolean; - showAggregateTabs: boolean; } diff --git a/services/libs/data-access-layer/src/collections/index.ts b/services/libs/data-access-layer/src/collections/index.ts index 297a18ab49..be5bc75aa9 100644 --- a/services/libs/data-access-layer/src/collections/index.ts +++ b/services/libs/data-access-layer/src/collections/index.ts @@ -21,7 +21,6 @@ export interface ICreateCollection { name: string slug?: string starred: boolean - showAggregateTabs?: boolean isPrivate?: boolean ssoUserId?: string | null logoUrl?: string | null @@ -94,7 +93,6 @@ export enum CollectionField { SLUG = 'slug', SSO_USER_ID = 'ssoUserId', STARRED = 'starred', - SHOW_AGGREGATE_TABS = 'showAggregateTabs', UPDATED_AT = 'updatedAt', DELETED_AT = 'deletedAt', } @@ -146,16 +144,12 @@ export async function createCollection( logoUrl: null, imageUrl: null, color: null, - // Only LF Foundation (curated) collections get aggregate tabs by default. - // ssoUserId is only set for community/user-curated collections, so - // ssoUserId being unset identifies curated collections (see V1784026542). - showAggregateTabs: !collection.ssoUserId, ...collection, } return qx.selectOne( ` - INSERT INTO collections (name, description, slug, "categoryId", starred, "logoUrl", "imageUrl", color, "showAggregateTabs") - VALUES ($(name), $(description), $(slug), $(categoryId), $(starred), $(logoUrl), $(imageUrl), $(color), $(showAggregateTabs)) + INSERT INTO collections (name, description, slug, "categoryId", starred, "logoUrl", "imageUrl", color) + VALUES ($(name), $(description), $(slug), $(categoryId), $(starred), $(logoUrl), $(imageUrl), $(color)) RETURNING * `, data,