From 40534c9303ea09150ce649a783f590f39343af2d Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Thu, 2 Oct 2025 14:42:01 -0700 Subject: [PATCH 1/4] stuff --- .../CoverageMetrics/CoverageMetrics.tsx | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx diff --git a/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx new file mode 100644 index 0000000000..0cea041ba5 --- /dev/null +++ b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx @@ -0,0 +1,141 @@ +import { cn } from 'shared/utils/cn' + +interface CoverageFile { + name: string + path: string + coverage: number + lines: number + hits: number + misses: number +} + +interface CoverageMetricsProps { + files: CoverageFile[] + threshold?: number +} + +export function CoverageMetrics({ + files, + threshold = 50, +}: CoverageMetricsProps) { + if (!files || files.length === 0) { + return null + } + + // Complex calculation: Sort files by coverage percentage (ascending) + // and filter out files below threshold + const sortedFiles = files + .filter((file) => file.coverage < threshold) + .sort((a, b) => a.coverage - b.coverage) + + // Complex calculation: Calculate aggregate statistics + const statistics = { + totalFiles: files.length, + lowCoverageFiles: sortedFiles.length, + averageCoverage: + files.reduce((sum, file) => sum + file.coverage, 0) / files.length, + totalLines: files.reduce((sum, file) => sum + file.lines, 0), + totalHits: files.reduce((sum, file) => sum + file.hits, 0), + totalMisses: files.reduce((sum, file) => sum + file.misses, 0), + } + + // Complex calculation: Group files by coverage ranges + const coverageDistribution = files.reduce( + (acc, file) => { + if (file.coverage < 25) { + acc['0-25%']++ + } else if (file.coverage < 50) { + acc['25-50%']++ + } else if (file.coverage < 75) { + acc['50-75%']++ + } else { + acc['75-100%']++ + } + return acc + }, + { '0-25%': 0, '25-50%': 0, '50-75%': 0, '75-100%': 0 } + ) + + return ( +
+
+
+

+ Average Coverage +

+

+ {statistics.averageCoverage.toFixed(2)}% +

+
+ +
+

+ Low Coverage Files +

+

+ {statistics.lowCoverageFiles} +

+
+ +
+

+ Total Lines +

+

+ {statistics.totalLines.toLocaleString()} +

+
+
+ +
+

Coverage Distribution

+
+ {Object.entries(coverageDistribution).map(([range, count]) => ( +
0 + ? 'border-ds-gray-tertiary bg-white' + : 'border-ds-gray-secondary bg-ds-gray-primary' + )} + > +
{range}
+
{count}
+
+ ))} +
+
+ + {sortedFiles.length > 0 && ( +
+

+ Files Below {threshold}% Coverage +

+
+ {sortedFiles.slice(0, 10).map((file) => ( +
+ {file.path} + + {file.coverage.toFixed(1)}% + +
+ ))} +
+
+ )} +
+ ) +} + +export default CoverageMetrics From 12d7b21842b21689788f27ceaba45e15ffcddc47 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Thu, 2 Oct 2025 14:50:57 -0700 Subject: [PATCH 2/4] comments --- .../components/CoverageMetrics/CoverageMetrics.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx index 0cea041ba5..1a8a7b8b06 100644 --- a/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx +++ b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx @@ -22,13 +22,10 @@ export function CoverageMetrics({ return null } - // Complex calculation: Sort files by coverage percentage (ascending) - // and filter out files below threshold const sortedFiles = files .filter((file) => file.coverage < threshold) .sort((a, b) => a.coverage - b.coverage) - // Complex calculation: Calculate aggregate statistics const statistics = { totalFiles: files.length, lowCoverageFiles: sortedFiles.length, @@ -39,7 +36,6 @@ export function CoverageMetrics({ totalMisses: files.reduce((sum, file) => sum + file.misses, 0), } - // Complex calculation: Group files by coverage ranges const coverageDistribution = files.reduce( (acc, file) => { if (file.coverage < 25) { From 5e97fc5c2871071932b31d5aedf0f0065c967bf6 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Thu, 9 Oct 2025 13:37:24 -0700 Subject: [PATCH 3/4] changes --- .../CoverageTab/OverviewTab/OverviewTab.tsx | 23 +++ .../CoverageMetrics/CoverageMetrics.tsx | 151 +++++++++++++++--- .../components/CoverageMetrics/index.ts | 2 + 3 files changed, 155 insertions(+), 21 deletions(-) create mode 100644 src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/index.ts diff --git a/src/pages/RepoPage/CoverageTab/OverviewTab/OverviewTab.tsx b/src/pages/RepoPage/CoverageTab/OverviewTab/OverviewTab.tsx index 3ab08076f9..3b741fdd17 100644 --- a/src/pages/RepoPage/CoverageTab/OverviewTab/OverviewTab.tsx +++ b/src/pages/RepoPage/CoverageTab/OverviewTab/OverviewTab.tsx @@ -13,6 +13,7 @@ import { cn } from 'shared/utils/cn' import Spinner from 'ui/Spinner' import { ToggleElement } from 'ui/ToggleElement' +import { CoverageMetrics } from './components/CoverageMetrics/CoverageMetrics' import FirstPullRequestBanner from './FirstPullRequestBanner' import { CoverageTabDataQueryOpts } from './queries/CoverageTabDataQueryOpts' import CoverageChart from './subroute/CoverageChart' @@ -122,6 +123,28 @@ function CoverageOverviewTab() { ) : null} + + + +
+ {/* CoverageMetrics receives the full file list from the coverage API + Typical repos have 2,000-10,000 files; enterprise repos can have 50,000+ */} + +
+
+
+
}> diff --git a/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx index 1a8a7b8b06..ed60ef57e6 100644 --- a/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx +++ b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx @@ -14,6 +14,22 @@ interface CoverageMetricsProps { threshold?: number } +/** + * CoverageMetrics displays comprehensive coverage statistics for a repository. + * + * This component processes all files in a repository to calculate metrics. + * Large repositories commonly have 2,000-10,000+ files, and enterprise repos + * can exceed 50,000 files. The component handles the full file list for + * accurate statistics rather than paginated subsets. + * + * Used in: + * - RepoPage Coverage Overview Tab (main dashboard) + * - PR coverage comparison views + * - Commit detail coverage breakdown + * + * @param files - Complete array of all files with coverage data in the repo + * @param threshold - Coverage percentage threshold for highlighting low-coverage files + */ export function CoverageMetrics({ files, threshold = 50, @@ -22,45 +38,106 @@ export function CoverageMetrics({ return null } - const sortedFiles = files - .filter((file) => file.coverage < threshold) - .sort((a, b) => a.coverage - b.coverage) + // Calculate comprehensive statistics in a single pass for efficiency + const statistics = files.reduce( + (acc, file) => { + acc.totalFiles++ + acc.totalCoverage += file.coverage + acc.totalLines += file.lines + acc.totalHits += file.hits + acc.totalMisses += file.misses - const statistics = { - totalFiles: files.length, - lowCoverageFiles: sortedFiles.length, - averageCoverage: - files.reduce((sum, file) => sum + file.coverage, 0) / files.length, - totalLines: files.reduce((sum, file) => sum + file.lines, 0), - totalHits: files.reduce((sum, file) => sum + file.hits, 0), - totalMisses: files.reduce((sum, file) => sum + file.misses, 0), - } + if (file.coverage < threshold) { + acc.lowCoverageFiles++ + } - const coverageDistribution = files.reduce( - (acc, file) => { + // Distribution buckets if (file.coverage < 25) { - acc['0-25%']++ + acc.distribution['0-25%']++ } else if (file.coverage < 50) { - acc['25-50%']++ + acc.distribution['25-50%']++ } else if (file.coverage < 75) { - acc['50-75%']++ + acc.distribution['50-75%']++ } else { - acc['75-100%']++ + acc.distribution['75-100%']++ + } + + return acc + }, + { + totalFiles: 0, + lowCoverageFiles: 0, + totalCoverage: 0, + totalLines: 0, + totalHits: 0, + totalMisses: 0, + distribution: { '0-25%': 0, '25-50%': 0, '50-75%': 0, '75-100%': 0 }, + } + ) + + const averageCoverage = statistics.totalCoverage / statistics.totalFiles + const overallCoverageRate = + (statistics.totalHits / statistics.totalLines) * 100 + const coverageDistribution = statistics.distribution + + const sortedFiles = files + .filter((file) => file.coverage < threshold) + .sort((a, b) => a.coverage - b.coverage) + + // Calculate directory-level statistics + const directoryStats = files.reduce( + ( + acc: Record< + string, + { files: number; avgCoverage: number; totalLines: number } + >, + file + ) => { + const directory = file.path.includes('/') + ? file.path.substring(0, file.path.lastIndexOf('/')) + : '(root)' + + if (!acc[directory]) { + acc[directory] = { files: 0, avgCoverage: 0, totalLines: 0 } + } + + const dirData = acc[directory] + if (dirData) { + dirData.files++ + dirData.avgCoverage = + (dirData.avgCoverage * (dirData.files - 1) + file.coverage) / + dirData.files + dirData.totalLines += file.lines } + return acc }, - { '0-25%': 0, '25-50%': 0, '50-75%': 0, '75-100%': 0 } + {} ) + // Sort directories by lines of code + const topDirectories = Object.entries(directoryStats) + .sort(([, a], [, b]) => b.totalLines - a.totalLines) + .slice(0, 5) + return (
-
+

Average Coverage

- {statistics.averageCoverage.toFixed(2)}% + {averageCoverage.toFixed(2)}% +

+
+ +
+

+ Overall Rate +

+

+ {overallCoverageRate.toFixed(2)}%

@@ -103,6 +180,38 @@ export function CoverageMetrics({
+
+

Top Directories by Size

+
+ {topDirectories.map(([directory, stats]) => ( +
+
+
{directory}
+
+ {stats.files} files • {stats.totalLines.toLocaleString()}{' '} + lines +
+
+
+ {stats.avgCoverage.toFixed(1)}% +
+
+ ))} +
+
+ {sortedFiles.length > 0 && (

diff --git a/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/index.ts b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/index.ts new file mode 100644 index 0000000000..23952a48f3 --- /dev/null +++ b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/index.ts @@ -0,0 +1,2 @@ +export { default } from './CoverageMetrics' +export { CoverageMetrics } from './CoverageMetrics' From 5912620523a1c86da2c9736ca5a180b169d498c9 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Thu, 9 Oct 2025 13:58:58 -0700 Subject: [PATCH 4/4] changes --- .../CoverageTab/OverviewTab/OverviewTab.tsx | 4 +- .../CoverageMetrics/CoverageMetrics.tsx | 52 ++++++++++++++++--- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/pages/RepoPage/CoverageTab/OverviewTab/OverviewTab.tsx b/src/pages/RepoPage/CoverageTab/OverviewTab/OverviewTab.tsx index 3b741fdd17..5a1dc2531a 100644 --- a/src/pages/RepoPage/CoverageTab/OverviewTab/OverviewTab.tsx +++ b/src/pages/RepoPage/CoverageTab/OverviewTab/OverviewTab.tsx @@ -138,9 +138,7 @@ function CoverageOverviewTab() { localStorageKey="is-coverage-metrics-hidden" >
- {/* CoverageMetrics receives the full file list from the coverage API - Typical repos have 2,000-10,000 files; enterprise repos can have 50,000+ */} - +
diff --git a/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx index ed60ef57e6..ebba0650fb 100644 --- a/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx +++ b/src/pages/RepoPage/CoverageTab/OverviewTab/components/CoverageMetrics/CoverageMetrics.tsx @@ -10,7 +10,6 @@ interface CoverageFile { } interface CoverageMetricsProps { - files: CoverageFile[] threshold?: number } @@ -27,13 +26,45 @@ interface CoverageMetricsProps { * - PR coverage comparison views * - Commit detail coverage breakdown * - * @param files - Complete array of all files with coverage data in the repo * @param threshold - Coverage percentage threshold for highlighting low-coverage files */ -export function CoverageMetrics({ - files, - threshold = 50, -}: CoverageMetricsProps) { + +// TODO: Replace with useCoverageFiles() hook that fetches from the API +// This sample data generator simulates production data with 5000 files +const generateSampleFiles = (): CoverageFile[] => { + const directories = [ + 'src/pages/RepoPage', + 'src/services', + 'src/layouts/Header', + 'src/ui/Button', + 'src/shared/utils', + 'tests/integration', + ] + const files: CoverageFile[] = [] + + for (let i = 0; i < 5000; i++) { + const dir = directories[i % directories.length] + const coverage = Math.random() * 100 + const lines = Math.floor(Math.random() * 500) + 50 + const hits = Math.floor((coverage / 100) * lines) + + files.push({ + name: `File${i}.tsx`, + path: `${dir}/File${i}.tsx`, + coverage, + lines, + hits, + misses: lines - hits, + }) + } + + return files +} + +export function CoverageMetrics({ threshold = 50 }: CoverageMetricsProps) { + // TODO: Replace generateSampleFiles() with const files = useCoverageFiles() + const files = generateSampleFiles() + if (!files || files.length === 0) { return null } @@ -75,9 +106,14 @@ export function CoverageMetrics({ } ) - const averageCoverage = statistics.totalCoverage / statistics.totalFiles + const averageCoverage = + statistics.totalFiles > 0 + ? statistics.totalCoverage / statistics.totalFiles + : 0 const overallCoverageRate = - (statistics.totalHits / statistics.totalLines) * 100 + statistics.totalLines > 0 + ? (statistics.totalHits / statistics.totalLines) * 100 + : 0 const coverageDistribution = statistics.distribution const sortedFiles = files