diff --git a/services/libs/tinybird/datasources/health_score_v2_development_ds.datasource b/services/libs/tinybird/datasources/health_score_v2_development_ds.datasource index 94853aa2f6..daaa218dc9 100644 --- a/services/libs/tinybird/datasources/health_score_v2_development_ds.datasource +++ b/services/libs/tinybird/datasources/health_score_v2_development_ds.datasource @@ -4,10 +4,41 @@ DESCRIPTION > - `repoUrl` is the repository URL — the join key back to `repositories`. - `developmentActivityScoreV2` — NULL when covered sub-signal weight is <40% of the 25pt max (spec Layer 1 graceful degradation), otherwise the rescaled 0-25 score. + - `releaseCadenceScore`/`commitActivityScore`/`issueResolutionScore`/`prMergeScore` (IN-1212) are + the raw per-signal sub-scores before rescaling, as computed in `health_score_v2_development.pipe`. + - `releaseCadenceAvailable`/`issueResolutionAvailable`/`prMergeAvailable` (IN-1212) are the Layer 1 + coverage flags — 0 means the signal was `blocked` (no data), not scored 0. Commit activity has no + `Available` flag: it always contributes via the `repos.lastCommitAt` fallback (see pipe DESCRIPTION). + - `daysSinceLatest`/`daysBetweenRecent` (IN-1212) are the raw release-cadence signal; NULL when the + repo has no published packages with release dates. + - `commitsLast6m` (IN-1212) is the raw commit count behind `commitActivityScore`; NULL when the repo + has no `authored-commit` activity (the score then falls back to `lastCommitAt`). + - `lastCommitAt` (IN-1212) is the repo's last commit timestamp, used as the commit-activity fallback. + - `closed12m`/`opened12m`/`medianCloseS` (IN-1212) are the raw issue-resolution counts/median behind + `issueResolutionScore`; NULL when unavailable. + - `merged12m`/`closedUnmerged12m`/`medianMergeS` (IN-1212) are the raw PR-merge counts/median behind + `prMergeScore`; NULL when unavailable. SCHEMA > `repoUrl` String, - `developmentActivityScoreV2` Nullable(UInt8) + `developmentActivityScoreV2` Nullable(UInt8), + `releaseCadenceScore` UInt8, + `releaseCadenceAvailable` UInt8, + `daysSinceLatest` Nullable(Int64), + `daysBetweenRecent` Nullable(Int64), + `commitActivityScore` UInt8, + `commitsLast6m` Nullable(UInt64), + `lastCommitAt` Nullable(DateTime64(3)), + `issueResolutionScore` UInt8, + `issueResolutionAvailable` UInt8, + `closed12m` Nullable(UInt64), + `opened12m` Nullable(UInt64), + `medianCloseS` Nullable(Float64), + `prMergeScore` UInt8, + `prMergeAvailable` UInt8, + `merged12m` Nullable(UInt64), + `closedUnmerged12m` Nullable(UInt64), + `medianMergeS` Nullable(Float64) ENGINE MergeTree ENGINE_SORTING_KEY repoUrl diff --git a/services/libs/tinybird/datasources/health_score_v2_maintainer_ds.datasource b/services/libs/tinybird/datasources/health_score_v2_maintainer_ds.datasource index caf3d523be..46def9226d 100644 --- a/services/libs/tinybird/datasources/health_score_v2_maintainer_ds.datasource +++ b/services/libs/tinybird/datasources/health_score_v2_maintainer_ds.datasource @@ -4,10 +4,36 @@ DESCRIPTION > - `repoUrl` is the repository URL — the join key back to `repositories`. - `maintainerHealthScoreV2` — NULL when covered sub-signal weight is <40% of the 40pt max (spec Layer 1 graceful degradation), otherwise the rescaled 0-40 score. + - `busFactorScore`/`orgDiversityScore`/`responsivenessScore` (IN-1212) are the raw per-signal + sub-scores before rescaling, as computed in `health_score_v2_maintainer.pipe`. + - `busFactorAvailable`/`orgDiversityAvailable`/`responsivenessAvailable` (IN-1212) are the Layer 1 + coverage flags — 0 means the signal was `blocked` (no data), not scored 0. + - `busFactorCount`/`orgCount` (IN-1212) are the raw counts behind the bus-factor and org-diversity + scores; NULL when the signal is blocked. + - `medianPrResponseS`/`medianIssueResponseS` (IN-1212) are the raw median response times (seconds) + behind the responsiveness score; NULL when unavailable. + - `isGerrit`/`isExcluded` (IN-1212) are the repo classification flags used to decide responsiveness + availability and scoring branch. + - `coveredWeight` (IN-1212) is the sum of available sub-signal weights (max 40) used as the + rescale denominator for `maintainerHealthScoreV2` — exposed so the content spec's "category + coverage" UI can show what fraction of the 40pt max was actually scored. SCHEMA > `repoUrl` String, - `maintainerHealthScoreV2` Nullable(UInt8) + `maintainerHealthScoreV2` Nullable(UInt8), + `busFactorScore` UInt8, + `busFactorAvailable` UInt8, + `busFactorCount` Nullable(UInt64), + `orgDiversityScore` UInt8, + `orgDiversityAvailable` UInt8, + `orgCount` Nullable(UInt64), + `responsivenessScore` UInt8, + `responsivenessAvailable` UInt8, + `medianPrResponseS` Nullable(Float64), + `medianIssueResponseS` Nullable(Float64), + `isGerrit` UInt8, + `isExcluded` UInt8, + `coveredWeight` UInt8 ENGINE MergeTree ENGINE_SORTING_KEY repoUrl diff --git a/services/libs/tinybird/datasources/health_score_v2_security_ds.datasource b/services/libs/tinybird/datasources/health_score_v2_security_ds.datasource index 8e7696eeb8..09fee28c01 100644 --- a/services/libs/tinybird/datasources/health_score_v2_security_ds.datasource +++ b/services/libs/tinybird/datasources/health_score_v2_security_ds.datasource @@ -4,10 +4,40 @@ DESCRIPTION > - `repoUrl` is the repository URL — the join key back to `repositories`. - `securitySupplyChainScoreV2` — NULL when covered sub-signal weight is <40% of the 35pt max (spec Layer 1 graceful degradation), otherwise the rescaled 0-35 score. + - `openVulnScore`/`scorecardScorePts`/`securityPracticesScore`/`dependencyHealthScore` (IN-1212) + are the raw per-signal sub-scores before rescaling, as computed in `health_score_v2_security.pipe`. + - `scorecardAvailable`/`securityPracticesAvailable`/`dependencyHealthAvailable` (IN-1212) are the + Layer 1 coverage flags — 0 means the signal was `blocked` (no data), not scored 0. + - `openCriticals`/`openHighs`/`openModerates` (IN-1212) are the raw open-vulnerability counts by + severity; NULL when the repo has no rows in `vulnerabilities`. + - `scorecardScore` (IN-1212) is the raw OpenSSF Scorecard aggregate score (0-10 as a string, + passed through from `repos.scorecardScore`; empty string when unavailable). + - `securityPolicyEnabled`/`branchProtectionEnabled`/`branchProtectionRequiredReviews`/ + `branchProtectionRequiresStatusChecks`/`branchProtectionAllowsForcePush` (IN-1212) are the raw + security-practice flags behind `securityPracticesScore`; NULL when unavailable. + - `vulnerableDeps` (IN-1212) is the raw count of dependencies with an open HIGH/CRITICAL advisory + behind `dependencyHealthScore`; NULL when the repo has no published packages. SCHEMA > `repoUrl` String, - `securitySupplyChainScoreV2` Nullable(UInt8) + `securitySupplyChainScoreV2` Nullable(UInt8), + `openVulnScore` UInt8, + `openCriticals` Nullable(UInt64), + `openHighs` Nullable(UInt64), + `openModerates` Nullable(UInt64), + `scorecardScorePts` UInt8, + `scorecardAvailable` UInt8, + `scorecardScore` String, + `securityPracticesScore` UInt8, + `securityPracticesAvailable` UInt8, + `securityPolicyEnabled` Nullable(UInt8), + `branchProtectionEnabled` Nullable(UInt8), + `branchProtectionRequiredReviews` Nullable(Int32), + `branchProtectionRequiresStatusChecks` Nullable(UInt8), + `branchProtectionAllowsForcePush` Nullable(UInt8), + `dependencyHealthScore` UInt8, + `dependencyHealthAvailable` UInt8, + `vulnerableDeps` Nullable(UInt64) ENGINE MergeTree ENGINE_SORTING_KEY repoUrl diff --git a/services/libs/tinybird/datasources/health_score_v2_signal_detail_ds.datasource b/services/libs/tinybird/datasources/health_score_v2_signal_detail_ds.datasource new file mode 100644 index 0000000000..f6bf42ff0e --- /dev/null +++ b/services/libs/tinybird/datasources/health_score_v2_signal_detail_ds.datasource @@ -0,0 +1,77 @@ +DESCRIPTION > + - `health_score_v2_signal_detail_ds` holds the per-repo flat signal-detail row for Health Score v2 + (IN-1212 Content Spec) — every promoted per-signal raw value and `*Available` coverage flag from + the 3 category datasources (`health_score_v2_maintainer_ds`, `health_score_v2_security_ds`, + `health_score_v2_development_ds`), joined on `repoUrl`. Populated by + `health_score_v2_signal_detail.pipe`. + - `repoUrl` is the repository URL — the join key back to `repositories`. + - Maintainer signals: `busFactorScore`, `busFactorAvailable`, `busFactorCount`, + `orgDiversityScore`, `orgDiversityAvailable`, `orgCount`, `responsivenessScore`, + `responsivenessAvailable`, `medianPrResponseS`, `medianIssueResponseS`, `isGerrit`, `isExcluded` + — see `health_score_v2_maintainer_ds` for definitions. + - Security signals: `openVulnScore`, `openCriticals`, `openHighs`, `openModerates`, + `scorecardScorePts`, `scorecardAvailable`, `scorecardScore`, `securityPracticesScore`, + `securityPracticesAvailable`, `securityPolicyEnabled`, `branchProtectionEnabled`, + `branchProtectionRequiredReviews`, `branchProtectionRequiresStatusChecks`, + `branchProtectionAllowsForcePush`, `dependencyHealthScore`, `dependencyHealthAvailable`, + `vulnerableDeps` — see `health_score_v2_security_ds` for definitions. + - Development signals: `releaseCadenceScore`, `releaseCadenceAvailable`, `daysSinceLatest`, + `daysBetweenRecent`, `commitActivityScore`, `commitsLast6m`, `lastCommitAt`, + `issueResolutionScore`, `issueResolutionAvailable`, `closed12m`, `opened12m`, `medianCloseS`, + `prMergeScore`, `prMergeAvailable`, `merged12m`, `closedUnmerged12m`, `medianMergeS` — see + `health_score_v2_development_ds` for definitions. + - All columns except `repoUrl` are Nullable here (even ones that are non-Nullable in their source + `_ds`) because this table is built via LEFT JOIN — a repo missing from one of the 3 source + datasources surfaces NULL for that category's columns rather than dropping the repo. + +SCHEMA > + `repoUrl` String, + `busFactorScore` Nullable(UInt8), + `busFactorAvailable` Nullable(UInt8), + `busFactorCount` Nullable(UInt64), + `orgDiversityScore` Nullable(UInt8), + `orgDiversityAvailable` Nullable(UInt8), + `orgCount` Nullable(UInt64), + `responsivenessScore` Nullable(UInt8), + `responsivenessAvailable` Nullable(UInt8), + `medianPrResponseS` Nullable(Float64), + `medianIssueResponseS` Nullable(Float64), + `isGerrit` Nullable(UInt8), + `isExcluded` Nullable(UInt8), + `openVulnScore` Nullable(UInt8), + `openCriticals` Nullable(UInt64), + `openHighs` Nullable(UInt64), + `openModerates` Nullable(UInt64), + `scorecardScorePts` Nullable(UInt8), + `scorecardAvailable` Nullable(UInt8), + `scorecardScore` Nullable(String), + `securityPracticesScore` Nullable(UInt8), + `securityPracticesAvailable` Nullable(UInt8), + `securityPolicyEnabled` Nullable(UInt8), + `branchProtectionEnabled` Nullable(UInt8), + `branchProtectionRequiredReviews` Nullable(Int32), + `branchProtectionRequiresStatusChecks` Nullable(UInt8), + `branchProtectionAllowsForcePush` Nullable(UInt8), + `dependencyHealthScore` Nullable(UInt8), + `dependencyHealthAvailable` Nullable(UInt8), + `vulnerableDeps` Nullable(UInt64), + `releaseCadenceScore` Nullable(UInt8), + `releaseCadenceAvailable` Nullable(UInt8), + `daysSinceLatest` Nullable(Int64), + `daysBetweenRecent` Nullable(Int64), + `commitActivityScore` Nullable(UInt8), + `commitsLast6m` Nullable(UInt64), + `lastCommitAt` Nullable(DateTime64(3)), + `issueResolutionScore` Nullable(UInt8), + `issueResolutionAvailable` Nullable(UInt8), + `closed12m` Nullable(UInt64), + `opened12m` Nullable(UInt64), + `medianCloseS` Nullable(Float64), + `prMergeScore` Nullable(UInt8), + `prMergeAvailable` Nullable(UInt8), + `merged12m` Nullable(UInt64), + `closedUnmerged12m` Nullable(UInt64), + `medianMergeS` Nullable(Float64) + +ENGINE MergeTree +ENGINE_SORTING_KEY repoUrl diff --git a/services/libs/tinybird/datasources/project_insights_health_breakdown_ds.datasource b/services/libs/tinybird/datasources/project_insights_health_breakdown_ds.datasource new file mode 100644 index 0000000000..00efad93af --- /dev/null +++ b/services/libs/tinybird/datasources/project_insights_health_breakdown_ds.datasource @@ -0,0 +1,81 @@ +DESCRIPTION > + - `project_insights_health_breakdown_ds` contains the materialized project-level Health Score v2 + signal breakdown (IN-1212), populated by `project_insights_health_breakdown_copy.pipe` on a + schedule. + - Precomputed so the public `project_insights_health_breakdown.pipe` endpoint is a cheap filtered + SELECT rather than a live per-request rollup — see that pipe's DESCRIPTION and + `project_insights_health_breakdown_copy.pipe`'s DESCRIPTION for the full per-column aggregation + methodology (score columns avg, count columns max, median-seconds columns avg, boolean/flag + columns max, `isGerrit`/`isExcluded` min). + - `projectId` is the project id (`insightsProjects.id`). + - `slug` is the project's URL-friendly identifier — the primary filter key for the endpoint. + - Maintainer signals: `busFactorScore` (avg), `busFactorAvailable` (max), `busFactorCount` (max), + `orgDiversityScore` (avg), `orgDiversityAvailable` (max), `orgCount` (max), + `responsivenessScore` (avg), `responsivenessAvailable` (max), `medianPrResponseS` (avg), + `medianIssueResponseS` (avg), `isGerrit` (min), `isExcluded` (min). + - Security signals: `openVulnScore` (avg), `openCriticals`/`openHighs`/`openModerates` (max), + `scorecardScorePts` (avg), `scorecardAvailable` (max), `scorecardScore` (avg, cast from the raw + String), `securityPracticesScore` (avg), `securityPracticesAvailable` (max), + `securityPolicyEnabled`/`branchProtectionEnabled`/`branchProtectionRequiredReviews`/ + `branchProtectionRequiresStatusChecks`/`branchProtectionAllowsForcePush` (max), + `dependencyHealthScore` (avg), `dependencyHealthAvailable` (max), `vulnerableDeps` (max). + - Development signals: `releaseCadenceScore` (avg), `releaseCadenceAvailable` (max), + `daysSinceLatest`/`daysBetweenRecent` (max), `commitActivityScore` (avg), `commitsLast6m` (max), + `lastCommitAt` (max), `issueResolutionScore` (avg), `issueResolutionAvailable` (max), + `closed12m`/`opened12m` (max), `medianCloseS` (avg), `prMergeScore` (avg), `prMergeAvailable` + (max), `merged12m`/`closedUnmerged12m` (max), `medianMergeS` (avg). + - All columns except `projectId`/`slug` are Nullable: a project with zero enabled/non-excluded + repos, or repos with no Health Score v2 signal data, rolls up to NULL rather than a fabricated 0. + +SCHEMA > + `projectId` String, + `slug` String, + `busFactorScore` Nullable(Float64), + `busFactorAvailable` Nullable(UInt8), + `busFactorCount` Nullable(UInt64), + `orgDiversityScore` Nullable(Float64), + `orgDiversityAvailable` Nullable(UInt8), + `orgCount` Nullable(UInt64), + `responsivenessScore` Nullable(Float64), + `responsivenessAvailable` Nullable(UInt8), + `medianPrResponseS` Nullable(Float64), + `medianIssueResponseS` Nullable(Float64), + `isGerrit` Nullable(UInt8), + `isExcluded` Nullable(UInt8), + `openVulnScore` Nullable(Float64), + `openCriticals` Nullable(UInt64), + `openHighs` Nullable(UInt64), + `openModerates` Nullable(UInt64), + `scorecardScorePts` Nullable(Float64), + `scorecardAvailable` Nullable(UInt8), + `scorecardScore` Nullable(Float64), + `securityPracticesScore` Nullable(Float64), + `securityPracticesAvailable` Nullable(UInt8), + `securityPolicyEnabled` Nullable(UInt8), + `branchProtectionEnabled` Nullable(UInt8), + `branchProtectionRequiredReviews` Nullable(Int32), + `branchProtectionRequiresStatusChecks` Nullable(UInt8), + `branchProtectionAllowsForcePush` Nullable(UInt8), + `dependencyHealthScore` Nullable(Float64), + `dependencyHealthAvailable` Nullable(UInt8), + `vulnerableDeps` Nullable(UInt64), + `releaseCadenceScore` Nullable(Float64), + `releaseCadenceAvailable` Nullable(UInt8), + `daysSinceLatest` Nullable(Int64), + `daysBetweenRecent` Nullable(Int64), + `commitActivityScore` Nullable(Float64), + `commitsLast6m` Nullable(UInt64), + `lastCommitAt` Nullable(DateTime64(3)), + `issueResolutionScore` Nullable(Float64), + `issueResolutionAvailable` Nullable(UInt8), + `closed12m` Nullable(UInt64), + `opened12m` Nullable(UInt64), + `medianCloseS` Nullable(Float64), + `prMergeScore` Nullable(Float64), + `prMergeAvailable` Nullable(UInt8), + `merged12m` Nullable(UInt64), + `closedUnmerged12m` Nullable(UInt64), + `medianMergeS` Nullable(Float64) + +ENGINE MergeTree +ENGINE_SORTING_KEY slug, projectId diff --git a/services/libs/tinybird/pipes/health_score_v2_development.pipe b/services/libs/tinybird/pipes/health_score_v2_development.pipe index 4829cb3e19..e9e63bbd78 100644 --- a/services/libs/tinybird/pipes/health_score_v2_development.pipe +++ b/services/libs/tinybird/pipes/health_score_v2_development.pipe @@ -19,11 +19,45 @@ SQL > coveredWeight / 25.0 < 0.4, NULL, toNullable(toUInt8(round(least(25.0, rawScore * (25.0 / coveredWeight))))) - ) AS developmentActivityScoreV2 + ) AS developmentActivityScoreV2, + releaseCadenceScore, + releaseCadenceAvailable, + daysSinceLatest, + daysBetweenRecent, + commitActivityScore, + commitsLast6m, + lastCommitAt, + issueResolutionScore, + issueResolutionAvailable, + closed12m, + opened12m, + medianCloseS, + prMergeScore, + prMergeAvailable, + merged12m, + closedUnmerged12m, + medianMergeS FROM ( SELECT repoUrl, + releaseCadenceScore, + releaseCadenceAvailable, + daysSinceLatest, + daysBetweenRecent, + commitActivityScore, + commitsLast6m, + lastCommitAt, + issueResolutionScore, + issueResolutionAvailable, + closed12m, + opened12m, + medianCloseS, + prMergeScore, + prMergeAvailable, + merged12m, + closedUnmerged12m, + medianMergeS, ( releaseCadenceAvailable * releaseCadenceScore + commitActivityScore @@ -40,7 +74,10 @@ SQL > ( SELECT rp.repoUrl AS repoUrl, + rp.lastCommitAt AS lastCommitAt, (rs.repoUrl != '') AS releaseCadenceAvailable, + rs.daysSinceLatest AS daysSinceLatest, + rs.daysBetweenRecent AS daysBetweenRecent, multiIf( rs.daysSinceLatest < 90 AND rs.daysBetweenRecent < 90, 8, @@ -52,6 +89,7 @@ SQL > 2, 0 ) AS releaseCadenceScore, + c.commitsLast6m AS commitsLast6m, -- commit activity is never `blocked`: it has its own spec-mandated fallback to -- repos.lastCommitAt, so it always contributes its full 5pt weight to coverage multiIf( @@ -74,7 +112,10 @@ SQL > 0 ) AS commitActivityScore, (ist.repoUrl != '') AS issueResolutionAvailable, - ( + ist.closed12m AS closed12m, + ist.opened12m AS opened12m, + ist.medianCloseS AS medianCloseS, + toUInt8( multiIf( ist.opened12m = 0, 0, @@ -87,7 +128,10 @@ SQL > + multiIf(ist.medianCloseS < 604800, 3, ist.medianCloseS < 2592000, 2, 0) ) AS issueResolutionScore, (prs.repoUrl != '') AS prMergeAvailable, - ( + prs.merged12m AS merged12m, + prs.closedUnmerged12m AS closedUnmerged12m, + prs.medianMergeS AS medianMergeS, + toUInt8( multiIf( (prs.merged12m + prs.closedUnmerged12m) = 0, 0, diff --git a/services/libs/tinybird/pipes/health_score_v2_maintainer.pipe b/services/libs/tinybird/pipes/health_score_v2_maintainer.pipe index a91775fc5f..1c2dc02eaa 100644 --- a/services/libs/tinybird/pipes/health_score_v2_maintainer.pipe +++ b/services/libs/tinybird/pipes/health_score_v2_maintainer.pipe @@ -37,19 +37,47 @@ SQL > coveredWeight / 40.0 < 0.4, NULL, toNullable(toUInt8(round(least(40.0, rawScore * (40.0 / coveredWeight))))) - ) AS maintainerHealthScoreV2 + ) AS maintainerHealthScoreV2, + base.busFactorScore AS busFactorScore, + base.busFactorAvailable AS busFactorAvailable, + base.busFactorCount AS busFactorCount, + base.orgDiversityScore AS orgDiversityScore, + base.orgDiversityAvailable AS orgDiversityAvailable, + base.orgCount AS orgCount, + base.responsivenessScore AS responsivenessScore, + base.responsivenessAvailable AS responsivenessAvailable, + base.medianPrResponseS AS medianPrResponseS, + base.medianIssueResponseS AS medianIssueResponseS, + base.isGerrit AS isGerrit, + base.isExcluded AS isExcluded, + coveredWeight AS coveredWeight FROM ( SELECT repoUrl, + busFactorScore, + busFactorAvailable, + busFactorCount, + orgDiversityScore, + orgDiversityAvailable, + orgCount, + responsivenessScore, + responsivenessAvailable, + medianPrResponseS, + medianIssueResponseS, + isGerrit, + isExcluded, (busFactorScore + orgDiversityScore + responsivenessScore) AS rawScore, - ( + toUInt8( busFactorAvailable * 18 + orgDiversityAvailable * 7 + responsivenessAvailable * 15 ) AS coveredWeight FROM ( SELECT allRepos.repoUrl AS repoUrl, + allRepos.isGerrit AS isGerrit, + allRepos.isExcluded AS isExcluded, + bf.busFactorCount AS busFactorCount, bf.repoUrl != '' AS busFactorAvailable, multiIf( bf.busFactorCount >= 5, @@ -62,6 +90,7 @@ SQL > 3, 0 ) AS busFactorScore, + od.orgCount AS orgCount, od.repoUrl != '' AS orgDiversityAvailable, multiIf( coalesce(od.orgCount, 0) >= 3, @@ -72,6 +101,8 @@ SQL > 2, 0 ) AS orgDiversityScore, + r.medianPrResponseS AS medianPrResponseS, + ir.medianIssueResponseS AS medianIssueResponseS, multiIf( allRepos.isExcluded, false, allRepos.isGerrit, r.repoUrl != '', true ) AS responsivenessAvailable, diff --git a/services/libs/tinybird/pipes/health_score_v2_security.pipe b/services/libs/tinybird/pipes/health_score_v2_security.pipe index d223c25beb..d1f303b60f 100644 --- a/services/libs/tinybird/pipes/health_score_v2_security.pipe +++ b/services/libs/tinybird/pipes/health_score_v2_security.pipe @@ -20,11 +20,45 @@ SQL > coveredWeight / 35.0 < 0.4, NULL, toNullable(toUInt8(round(least(35.0, rawScore * (35.0 / coveredWeight))))) - ) AS securitySupplyChainScoreV2 + ) AS securitySupplyChainScoreV2, + openVulnScore, + openCriticals, + openHighs, + openModerates, + scorecardScorePts, + scorecardAvailable, + scorecardScore, + securityPracticesScore, + securityPracticesAvailable, + securityPolicyEnabled, + branchProtectionEnabled, + branchProtectionRequiredReviews, + branchProtectionRequiresStatusChecks, + branchProtectionAllowsForcePush, + dependencyHealthScore, + dependencyHealthAvailable, + vulnerableDeps FROM ( SELECT repoUrl, + openVulnScore, + openCriticals, + openHighs, + openModerates, + scorecardScorePts, + scorecardAvailable, + scorecardScore, + securityPracticesScore, + securityPracticesAvailable, + securityPolicyEnabled, + branchProtectionEnabled, + branchProtectionRequiredReviews, + branchProtectionRequiresStatusChecks, + branchProtectionAllowsForcePush, + dependencyHealthScore, + dependencyHealthAvailable, + vulnerableDeps, ( openVulnScore + scorecardAvailable * scorecardScorePts @@ -41,18 +75,24 @@ SQL > ( SELECT allRepos.url AS repoUrl, - greatest( - 0, - 10 - - 6 * coalesce(vc.openCriticals, 0) - - 3 * coalesce(vc.openHighs, 0) - - 1 * coalesce(vc.openModerates, 0) + toUInt8( + greatest( + 0, + 10 + - 6 * coalesce(vc.openCriticals, 0) + - 3 * coalesce(vc.openHighs, 0) + - 1 * coalesce(vc.openModerates, 0) + ) ) AS openVulnScore, + vc.openCriticals AS openCriticals, + vc.openHighs AS openHighs, + vc.openModerates AS openModerates, toUInt8( round(least(toFloat64OrZero(rd.scorecardScore), 10) * 0.7) ) AS scorecardScorePts, (rd.url != '') AS scorecardAvailable, - ( + rd.scorecardScore AS scorecardScore, + toUInt8( 2 * coalesce(rd.securityPolicyEnabled, 0) + 2 * coalesce(rd.branchProtectionEnabled, 0) + if(coalesce(rd.branchProtectionRequiredReviews, 0) >= 1, 1, 0) @@ -60,6 +100,11 @@ SQL > + if(coalesce(rd.branchProtectionAllowsForcePush, 1) = 0, 1, 0) ) AS securityPracticesScore, (rd.url != '') AS securityPracticesAvailable, + rd.securityPolicyEnabled AS securityPolicyEnabled, + rd.branchProtectionEnabled AS branchProtectionEnabled, + rd.branchProtectionRequiredReviews AS branchProtectionRequiredReviews, + rd.branchProtectionRequiresStatusChecks AS branchProtectionRequiresStatusChecks, + rd.branchProtectionAllowsForcePush AS branchProtectionAllowsForcePush, multiIf( coalesce(dh.vulnerableDeps, 0) = 0, 5, @@ -69,7 +114,8 @@ SQL > 1, 0 ) AS dependencyHealthScore, - (pkgs.repoUrl != '') AS dependencyHealthAvailable + (pkgs.repoUrl != '') AS dependencyHealthAvailable, + dh.vulnerableDeps AS vulnerableDeps FROM (SELECT DISTINCT url FROM repositories WHERE deletedAt IS NULL) AS allRepos LEFT JOIN ( diff --git a/services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe b/services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe new file mode 100644 index 0000000000..87b91acd96 --- /dev/null +++ b/services/libs/tinybird/pipes/health_score_v2_signal_detail.pipe @@ -0,0 +1,79 @@ +DESCRIPTION > + - Combines the 3 widened Health Score v2 category datasources (maintainer/security/development, + each carrying their promoted per-signal raw values and `*Available` coverage flags as of IN-1212) + into a single flat per-repo row, for the Health Score v2 Content Spec's per-signal breakdown UI + (spec section 7 "Signal Rows" — the "blocked" dash/dimmed state renders directly off the + `*Available` flags carried here, no separate computation needed). + - Same join pattern as `health_score_v2.pipe`'s `health_score_v2_results` node: a base repo list + LEFT JOINed against each category datasource on `repoUrl`, so every repo is present even when a + category is entirely unscored. + - Schedule: `50 2 * * *`, moved post-review (epipav: "schedule these to later and make sure they + don't intersect with existing copies") from the original `25 2 * * *`. Real production data + (`tinybird.jobs_log` over 7 days) confirmed the original slot wasn't wrong in isolation — this + pipe runs in ~3 seconds — but it launched into the most crowded 20-40-minute stretch of hour 2 + (several long-running neighbors, e.g. `issue_analysis_copy_pipe` spanning ~14 minutes through + that window, `ossPackages_enriched` ~7 minutes), the same neighborhood as the one observed + hour-2 concurrent-copy-job cap error in that period. `50 2` lands in hour 2's emptiest observed + stretch (no other copy job scheduled after `:50`) with a comfortable margin before + `project_insights_health_breakdown_copy.pipe`'s `55 2` and `project_insights_copy.pipe`'s hard + `0 3 * * *` deadline (which reads from this pipe's output transitively). + +NODE health_score_v2_signal_detail_calc +SQL > + SELECT + base.repoUrl AS repoUrl, + m.busFactorScore AS busFactorScore, + m.busFactorAvailable AS busFactorAvailable, + m.busFactorCount AS busFactorCount, + m.orgDiversityScore AS orgDiversityScore, + m.orgDiversityAvailable AS orgDiversityAvailable, + m.orgCount AS orgCount, + m.responsivenessScore AS responsivenessScore, + m.responsivenessAvailable AS responsivenessAvailable, + m.medianPrResponseS AS medianPrResponseS, + m.medianIssueResponseS AS medianIssueResponseS, + m.isGerrit AS isGerrit, + m.isExcluded AS isExcluded, + s.openVulnScore AS openVulnScore, + s.openCriticals AS openCriticals, + s.openHighs AS openHighs, + s.openModerates AS openModerates, + s.scorecardScorePts AS scorecardScorePts, + s.scorecardAvailable AS scorecardAvailable, + s.scorecardScore AS scorecardScore, + s.securityPracticesScore AS securityPracticesScore, + s.securityPracticesAvailable AS securityPracticesAvailable, + s.securityPolicyEnabled AS securityPolicyEnabled, + s.branchProtectionEnabled AS branchProtectionEnabled, + s.branchProtectionRequiredReviews AS branchProtectionRequiredReviews, + s.branchProtectionRequiresStatusChecks AS branchProtectionRequiresStatusChecks, + s.branchProtectionAllowsForcePush AS branchProtectionAllowsForcePush, + s.dependencyHealthScore AS dependencyHealthScore, + s.dependencyHealthAvailable AS dependencyHealthAvailable, + s.vulnerableDeps AS vulnerableDeps, + d.releaseCadenceScore AS releaseCadenceScore, + d.releaseCadenceAvailable AS releaseCadenceAvailable, + d.daysSinceLatest AS daysSinceLatest, + d.daysBetweenRecent AS daysBetweenRecent, + d.commitActivityScore AS commitActivityScore, + d.commitsLast6m AS commitsLast6m, + d.lastCommitAt AS lastCommitAt, + d.issueResolutionScore AS issueResolutionScore, + d.issueResolutionAvailable AS issueResolutionAvailable, + d.closed12m AS closed12m, + d.opened12m AS opened12m, + d.medianCloseS AS medianCloseS, + d.prMergeScore AS prMergeScore, + d.prMergeAvailable AS prMergeAvailable, + d.merged12m AS merged12m, + d.closedUnmerged12m AS closedUnmerged12m, + d.medianMergeS AS medianMergeS + FROM (SELECT DISTINCT url AS repoUrl FROM repositories FINAL WHERE isNull (deletedAt)) AS base + LEFT JOIN health_score_v2_maintainer_ds AS m ON m.repoUrl = base.repoUrl + LEFT JOIN health_score_v2_security_ds AS s ON s.repoUrl = base.repoUrl + LEFT JOIN health_score_v2_development_ds AS d ON d.repoUrl = base.repoUrl + +TYPE COPY +TARGET_DATASOURCE health_score_v2_signal_detail_ds +COPY_MODE replace +COPY_SCHEDULE 50 2 * * * diff --git a/services/libs/tinybird/pipes/project_insights_health_breakdown.pipe b/services/libs/tinybird/pipes/project_insights_health_breakdown.pipe new file mode 100644 index 0000000000..45b0690326 --- /dev/null +++ b/services/libs/tinybird/pipes/project_insights_health_breakdown.pipe @@ -0,0 +1,75 @@ +DESCRIPTION > + - `project_insights_health_breakdown.pipe` serves the Health Score v2 per-signal breakdown rolled + up to project level, for the project overview page's Health breakdown section (IN-1212 Content + Spec sections 4-7: category tab descriptions and per-signal rows, including the `*Available` + graceful-degradation flags for the "Unavailable — category dropped" and per-signal "blocked" dash + states). + - Performance: this endpoint reads from the pre-materialized `project_insights_health_breakdown_ds` + (populated on a schedule by `project_insights_health_breakdown_copy.pipe`) rather than computing + the rollup live, matching the pattern `project_insights_impact_breakdown.pipe` uses against + `project_insights_impact_breakdown_ds`. + - Parameters: `slug` (single project). + +TOKEN "insights-app-token" READ + +TAGS "Insights, Widget", "Project", "Health" + +NODE project_insights_health_breakdown_endpoint +SQL > + % + SELECT + projectId, + slug, + busFactorScore, + busFactorAvailable, + busFactorCount, + orgDiversityScore, + orgDiversityAvailable, + orgCount, + responsivenessScore, + responsivenessAvailable, + medianPrResponseS, + medianIssueResponseS, + isGerrit, + isExcluded, + openVulnScore, + openCriticals, + openHighs, + openModerates, + scorecardScorePts, + scorecardAvailable, + scorecardScore, + securityPracticesScore, + securityPracticesAvailable, + securityPolicyEnabled, + branchProtectionEnabled, + branchProtectionRequiredReviews, + branchProtectionRequiresStatusChecks, + branchProtectionAllowsForcePush, + dependencyHealthScore, + dependencyHealthAvailable, + vulnerableDeps, + releaseCadenceScore, + releaseCadenceAvailable, + daysSinceLatest, + daysBetweenRecent, + commitActivityScore, + commitsLast6m, + lastCommitAt, + issueResolutionScore, + issueResolutionAvailable, + closed12m, + opened12m, + medianCloseS, + prMergeScore, + prMergeAvailable, + merged12m, + closedUnmerged12m, + medianMergeS + FROM project_insights_health_breakdown_ds + WHERE + 1 = 1 + {% if defined(slug) %} + AND slug = {{ String(slug, description="Project slug", required=False) }} + {% end %} + ORDER BY slug ASC diff --git a/services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe b/services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe new file mode 100644 index 0000000000..0f5da451a3 --- /dev/null +++ b/services/libs/tinybird/pipes/project_insights_health_breakdown_copy.pipe @@ -0,0 +1,138 @@ +DESCRIPTION > + - `project_insights_health_breakdown_copy.pipe` is the scheduled `TYPE COPY` pipe that rolls up + the per-repo Health Score v2 signal detail (`health_score_v2_signal_detail_ds`, IN-1212) to + project level into `project_insights_health_breakdown_ds`, for the project overview page's + Health breakdown section (spec sections 4-7: category tab descriptions and per-signal rows). + Mirrors `project_insights_impact_breakdown_copy.pipe`'s overall structure (single rollup NODE, + `TYPE COPY`, scheduled after its upstream dependency). + - Join path: `repositories` (project's own repos, filtered `enabled = true AND excluded = false`) + -> `health_score_v2_signal_detail_ds` (matched on `repoUrl` = `rep.url`) — same population filter + and join shape as `project_insights_copy.pipe`'s `project_insights_copy_health_v2_project` node. + - Aggregation per column, chosen per column type (not one rule for all columns), per the IN-1212 + content-spec execution plan: + - Score columns with a matching `*Available` flag (`busFactorScore`, `orgDiversityScore`, + `responsivenessScore`, `scorecardScorePts`, `scorecardScore`, `securityPracticesScore`, + `dependencyHealthScore`, `releaseCadenceScore`, `issueResolutionScore`, `prMergeScore`): + `sumIf(score, available) / nullIf(countIf(available), 0)` — a coverage-filtered average that + excludes blocked repos entirely and yields NULL (not 0 or NaN) when no repo in the project has + the signal. Plain `avg()` was the original approach but is wrong: the underlying per-repo score + is a non-nullable `multiIf(..., 0)` (see `health_score_v2_maintainer/security/development.pipe`), + so a blocked repo's score is a real `0`, not NULL — `avg()` folds that placeholder zero into the + mean for any project with at least one available sibling repo, silently understating the + project-level score. (Fixed post-review.) + - `openVulnScore`/`commitActivityScore`: plain `avg()` — these 2 of 11 signals have no matching + `*Available` flag (confirmed against the category pipes' own `coveredWeight` SQL, which treats + both as unconditionally covered), so there's no blocked-repo case to filter out. + - Count columns (`busFactorCount`, `orgCount`, `openCriticals`, `openHighs`, `openModerates`, + `vulnerableDeps`, `commitsLast6m`, `closed12m`, `opened12m`, `merged12m`, `closedUnmerged12m`, + `branchProtectionRequiredReviews`): `max()` across repos — the most-staffed/most-active/ + most-protected repo is the meaningful project-level signal, matching the precedent set by + `project_insights_impact_breakdown_copy.pipe`'s `directDependents` MAX rollup and bus factor's + own per-repo tiering logic. + - `daysSinceLatest`/`daysBetweenRecent`: `min()`, not `max()` — these are inverse-activity + durations (per `health_score_v2_development.pipe`'s own scoring: smaller = more recent/frequent + releases = higher score). `max()` here would silently pick the stalest repo as the project-level + signal, contradicting the "most-active repo" framing above and the `lastCommitAt = max()` rollup + immediately below, where `max` correctly means "most recent timestamp." (Fixed post-review.) + - Median-seconds columns (`medianPrResponseS`, `medianIssueResponseS`, `medianCloseS`, + `medianMergeS`): `avg()` of the medians — acceptable approximation; an exact cross-repo median + would need raw response-time data, out of scope for this rollup. + - `scorecardScore`: stored as a String on the source datasource (matching `repos.scorecardScore`'s + convention), cast to Float64 and averaged like the other raw score-ish values. + - Boolean/flag columns (`busFactorAvailable`, `orgDiversityAvailable`, `responsivenessAvailable`, + `scorecardAvailable`, `securityPracticesAvailable`, `dependencyHealthAvailable`, + `releaseCadenceAvailable`, `issueResolutionAvailable`, `prMergeAvailable`, + `securityPolicyEnabled`, `branchProtectionEnabled`, `branchProtectionRequiresStatusChecks`, + `branchProtectionAllowsForcePush`): `max()` — true if any repo in the project has it. + - `isGerrit`/`isExcluded`: `min()` — false if any repo is a "normal" GitHub repo, since the + "not applicable" framing per spec is about the project's overall data availability, not a single + repo's classification. + - `lastCommitAt`: `max()` — the most recent commit across the project's repos. + - Schedule: `55 2 * * *`, moved post-review (epipav) from the original `35 2 * * *` — see + `health_score_v2_signal_detail.pipe`'s DESCRIPTION for the production-data-backed rationale + (real job-log analysis, not a guess). `health_score_v2_signal_detail.pipe` (populates the source + `_ds`) now runs at `50 2 * * *`; this pipe follows 5 minutes later, both observed at ~3-second + runtimes in production so the buffer is very conservative, and completes 5 minutes before + `project_insights_copy.pipe`'s hard `0 3 * * *` deadline (which reads this pipe's output). + +NODE project_insights_health_breakdown_raw +DESCRIPTION > + Project-level rollup of per-repo Health Score v2 signal detail, via repositories -> + health_score_v2_signal_detail_ds (same join mechanism and population filter as + `project_insights_copy.pipe`'s `project_insights_copy_health_v2_project` node). LEFT JOINed from + `insightsProjects` so every project is present in the output, even one with zero enabled/ + non-excluded repos. + +SQL > + SELECT + ip.id AS projectId, + ip.slug AS slug, + sumIf(sd.busFactorScore, sd.busFactorAvailable) + / nullIf(countIf(sd.busFactorAvailable), 0) AS busFactorScore, + max(sd.busFactorAvailable) AS busFactorAvailable, + max(sd.busFactorCount) AS busFactorCount, + sumIf(sd.orgDiversityScore, sd.orgDiversityAvailable) + / nullIf(countIf(sd.orgDiversityAvailable), 0) AS orgDiversityScore, + max(sd.orgDiversityAvailable) AS orgDiversityAvailable, + max(sd.orgCount) AS orgCount, + sumIf(sd.responsivenessScore, sd.responsivenessAvailable) + / nullIf(countIf(sd.responsivenessAvailable), 0) AS responsivenessScore, + max(sd.responsivenessAvailable) AS responsivenessAvailable, + avg(sd.medianPrResponseS) AS medianPrResponseS, + avg(sd.medianIssueResponseS) AS medianIssueResponseS, + min(sd.isGerrit) AS isGerrit, + min(sd.isExcluded) AS isExcluded, + avg(sd.openVulnScore) AS openVulnScore, + max(sd.openCriticals) AS openCriticals, + max(sd.openHighs) AS openHighs, + max(sd.openModerates) AS openModerates, + sumIf(sd.scorecardScorePts, sd.scorecardAvailable) + / nullIf(countIf(sd.scorecardAvailable), 0) AS scorecardScorePts, + max(sd.scorecardAvailable) AS scorecardAvailable, + sumIf(toFloat64OrNull(sd.scorecardScore), sd.scorecardAvailable) + / nullIf(countIf(sd.scorecardAvailable), 0) AS scorecardScore, + sumIf(sd.securityPracticesScore, sd.securityPracticesAvailable) + / nullIf(countIf(sd.securityPracticesAvailable), 0) AS securityPracticesScore, + max(sd.securityPracticesAvailable) AS securityPracticesAvailable, + max(sd.securityPolicyEnabled) AS securityPolicyEnabled, + max(sd.branchProtectionEnabled) AS branchProtectionEnabled, + max(sd.branchProtectionRequiredReviews) AS branchProtectionRequiredReviews, + max(sd.branchProtectionRequiresStatusChecks) AS branchProtectionRequiresStatusChecks, + max(sd.branchProtectionAllowsForcePush) AS branchProtectionAllowsForcePush, + sumIf(sd.dependencyHealthScore, sd.dependencyHealthAvailable) + / nullIf(countIf(sd.dependencyHealthAvailable), 0) AS dependencyHealthScore, + max(sd.dependencyHealthAvailable) AS dependencyHealthAvailable, + max(sd.vulnerableDeps) AS vulnerableDeps, + sumIf(sd.releaseCadenceScore, sd.releaseCadenceAvailable) + / nullIf(countIf(sd.releaseCadenceAvailable), 0) AS releaseCadenceScore, + max(sd.releaseCadenceAvailable) AS releaseCadenceAvailable, + min(sd.daysSinceLatest) AS daysSinceLatest, + min(sd.daysBetweenRecent) AS daysBetweenRecent, + avg(sd.commitActivityScore) AS commitActivityScore, + max(sd.commitsLast6m) AS commitsLast6m, + max(sd.lastCommitAt) AS lastCommitAt, + sumIf(sd.issueResolutionScore, sd.issueResolutionAvailable) + / nullIf(countIf(sd.issueResolutionAvailable), 0) AS issueResolutionScore, + max(sd.issueResolutionAvailable) AS issueResolutionAvailable, + max(sd.closed12m) AS closed12m, + max(sd.opened12m) AS opened12m, + avg(sd.medianCloseS) AS medianCloseS, + sumIf(sd.prMergeScore, sd.prMergeAvailable) + / nullIf(countIf(sd.prMergeAvailable), 0) AS prMergeScore, + max(sd.prMergeAvailable) AS prMergeAvailable, + max(sd.merged12m) AS merged12m, + max(sd.closedUnmerged12m) AS closedUnmerged12m, + avg(sd.medianMergeS) AS medianMergeS + FROM insightsProjects ip FINAL + LEFT JOIN + repositories rep FINAL + ON rep.insightsProjectId = ip.id + AND rep.enabled = true + AND rep.excluded = false + LEFT JOIN health_score_v2_signal_detail_ds sd ON sd.repoUrl = rep.url + GROUP BY ip.id, ip.slug + +TYPE COPY +TARGET_DATASOURCE project_insights_health_breakdown_ds +COPY_MODE replace +COPY_SCHEDULE 55 2 * * *