From 7e7c3d51d5c34ce5068eee913b180449924bc4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 25 Aug 2026 23:03:11 +0100 Subject: [PATCH 1/2] fix: handle unavailable open vulnerability score IN-1241 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getOpenVulnRow rendered a false 'no open vulnerabilities' positive when openVulnScore was null (Gerrit repos, excluded repos, or repos with no vuln data), instead of the blocked/no-data state used by sibling signal rows. Signed-off-by: Gašper Grom --- .../config/health-breakdown-templates.test.ts | 25 +++++++++++++++++++ frontend/config/health-breakdown-templates.ts | 6 +++++ 2 files changed, 31 insertions(+) create mode 100644 frontend/config/health-breakdown-templates.test.ts diff --git a/frontend/config/health-breakdown-templates.test.ts b/frontend/config/health-breakdown-templates.test.ts new file mode 100644 index 00000000..31f130c5 --- /dev/null +++ b/frontend/config/health-breakdown-templates.test.ts @@ -0,0 +1,25 @@ +/* +Copyright (c) 2026 The Linux Foundation and each contributor. +SPDX-License-Identifier: MIT +*/ + +import { describe, test, expect } from 'vitest'; +import type { HealthBreakdownResults } from '../types/overview/responses.types'; +import { getOpenVulnRow } from './health-breakdown-templates'; + +describe('health-breakdown-templates', () => { + test('getOpenVulnRow returns no-data when openVulnScore is null', () => { + const signals = { + openVulnScore: null, + openCriticals: null, + openHighs: null, + openModerates: null, + isGerrit: null, + isExcluded: null, + } as HealthBreakdownResults; + + const result = getOpenVulnRow(signals); + + expect(result.status).toBe('no-data'); + }); +}); diff --git a/frontend/config/health-breakdown-templates.ts b/frontend/config/health-breakdown-templates.ts index 033d791d..8ef92e61 100644 --- a/frontend/config/health-breakdown-templates.ts +++ b/frontend/config/health-breakdown-templates.ts @@ -373,6 +373,12 @@ export const getOrgDiversityRow = (signals: HealthBreakdownResults): SignalRow = // --- Security signals --- export const getOpenVulnRow = (signals: HealthBreakdownResults): SignalRow => { + if (signals.openVulnScore === null) { + return { + status: 'no-data', + description: blockedSignalDescription('Open vulnerabilities', signals), + }; + } const criticals = signals.openCriticals ?? 0; const highs = signals.openHighs ?? 0; const moderates = signals.openModerates ?? 0; From 76b90d64ef4cf008f2e58dc97324275a24dbe5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Grom?= Date: Tue, 25 Aug 2026 23:20:23 +0100 Subject: [PATCH 2/2] fix: use openVulnAvailable flag for open-vuln blocked state IN-1241 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gašper Grom --- .../config/health-breakdown-templates.test.ts | 33 +++++++++++++++++++ frontend/config/health-breakdown-templates.ts | 7 ++-- frontend/types/overview/responses.types.ts | 1 + 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/frontend/config/health-breakdown-templates.test.ts b/frontend/config/health-breakdown-templates.test.ts index 31f130c5..31e4dd0c 100644 --- a/frontend/config/health-breakdown-templates.test.ts +++ b/frontend/config/health-breakdown-templates.test.ts @@ -11,6 +11,7 @@ describe('health-breakdown-templates', () => { test('getOpenVulnRow returns no-data when openVulnScore is null', () => { const signals = { openVulnScore: null, + openVulnAvailable: null, openCriticals: null, openHighs: null, openModerates: null, @@ -22,4 +23,36 @@ describe('health-breakdown-templates', () => { expect(result.status).toBe('no-data'); }); + + test('getOpenVulnRow returns no-data when the repo has never completed a vulnerability scan', () => { + const signals = { + openVulnScore: 10, + openVulnAvailable: false, + openCriticals: null, + openHighs: null, + openModerates: null, + isGerrit: false, + isExcluded: false, + } as HealthBreakdownResults; + + const result = getOpenVulnRow(signals); + + expect(result.status).toBe('no-data'); + }); + + test('getOpenVulnRow returns positive when the repo has been scanned and is clean', () => { + const signals = { + openVulnScore: 10, + openVulnAvailable: true, + openCriticals: 0, + openHighs: 0, + openModerates: 0, + isGerrit: false, + isExcluded: false, + } as HealthBreakdownResults; + + const result = getOpenVulnRow(signals); + + expect(result.status).toBe('positive'); + }); }); diff --git a/frontend/config/health-breakdown-templates.ts b/frontend/config/health-breakdown-templates.ts index 8ef92e61..ec49cbe3 100644 --- a/frontend/config/health-breakdown-templates.ts +++ b/frontend/config/health-breakdown-templates.ts @@ -256,14 +256,15 @@ const getCategoryUnavailableDescription = ( } if (category === 'security') { const availableSignals: string[] = []; + if (signals?.openVulnAvailable) availableSignals.push('open vulnerability data'); if (signals?.scorecardAvailable) availableSignals.push('OpenSSF Scorecard'); if (signals?.securityPracticesAvailable) availableSignals.push('security practices'); if (signals?.dependencyHealthAvailable) availableSignals.push('dependency health'); const reason = signals?.isGerrit ? 'Gerrit-hosted projects' : 'this repository type'; const namedAvailable = availableSignals.length > 0 - ? `Only open vulnerability data and ${availableSignals.join(', ')} ${availableSignals.length === 1 ? 'is' : 'are'} available for ${reason}.` - : `Only open vulnerability data is available for ${reason}.`; + ? `Only ${availableSignals.join(', ')} ${availableSignals.length === 1 ? 'is' : 'are'} available for ${reason}.` + : `No security signals are available for ${reason}.`; return `${namedAvailable} The category has been dropped from the Health Score composite.`; } const availableSignals: string[] = []; @@ -373,7 +374,7 @@ export const getOrgDiversityRow = (signals: HealthBreakdownResults): SignalRow = // --- Security signals --- export const getOpenVulnRow = (signals: HealthBreakdownResults): SignalRow => { - if (signals.openVulnScore === null) { + if (!signals.openVulnAvailable || signals.openVulnScore === null) { return { status: 'no-data', description: blockedSignalDescription('Open vulnerabilities', signals), diff --git a/frontend/types/overview/responses.types.ts b/frontend/types/overview/responses.types.ts index 427e3fab..e177d059 100644 --- a/frontend/types/overview/responses.types.ts +++ b/frontend/types/overview/responses.types.ts @@ -105,6 +105,7 @@ export interface HealthBreakdownResults { // Security openVulnScore: number | null; + openVulnAvailable: boolean | null; openCriticals: number | null; openHighs: number | null; openModerates: number | null;