From b92713f8172fc7175d890c760f3fd1aeb6358017 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Thu, 2 Oct 2025 14:39:51 -0700 Subject: [PATCH 1/6] stuff --- .../TestResultsChart/TestResultsChart.tsx | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx diff --git a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx new file mode 100644 index 0000000000..7b36ec247b --- /dev/null +++ b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx @@ -0,0 +1,204 @@ +import { cn } from 'shared/utils/cn' + +interface TestResult { + id: string + name: string + duration: number + status: 'passed' | 'failed' | 'skipped' + timestamp: number + branch: string +} + +interface ChartDataPoint { + date: string + passed: number + failed: number + skipped: number + totalDuration: number +} + +interface TestResultsChartProps { + results: TestResult[] +} + +export function TestResultsChart({ results }: TestResultsChartProps) { + if (!results || results.length === 0) { + return null + } + + // Complex calculation: Transform test results into chart data by date + const chartData = results.reduce((acc: ChartDataPoint[], result) => { + const date = new Date(result.timestamp).toLocaleDateString() + const existing = acc.find((item) => item.date === date) + + if (existing) { + existing[result.status]++ + existing.totalDuration += result.duration + } else { + acc.push({ + date, + passed: result.status === 'passed' ? 1 : 0, + failed: result.status === 'failed' ? 1 : 0, + skipped: result.status === 'skipped' ? 1 : 0, + totalDuration: result.duration, + }) + } + + return acc + }, []) + + // Complex calculation: Find top 10 slowest tests + const slowestTests = results + .filter((result) => result.status !== 'skipped') + .sort((a, b) => b.duration - a.duration) + .slice(0, 10) + + // Complex calculation: Calculate statistics per branch + const branchStats = results.reduce( + ( + acc: Record< + string, + { total: number; failed: number; avgDuration: number } + >, + result + ) => { + if (!acc[result.branch]) { + acc[result.branch] = { total: 0, failed: 0, avgDuration: 0 } + } + acc[result.branch].total++ + if (result.status === 'failed') { + acc[result.branch].failed++ + } + acc[result.branch].avgDuration = + (acc[result.branch].avgDuration * (acc[result.branch].total - 1) + + result.duration) / + acc[result.branch].total + return acc + }, + {} + ) + + // Complex calculation: Calculate flakiness score for each test + const flakinessScores = results.reduce( + (acc: Record, result) => { + if (!acc[result.name]) { + acc[result.name] = 0 + } + // Count status changes as flakiness indicator + const testResults = results.filter((r) => r.name === result.name) + let statusChanges = 0 + for (let i = 1; i < testResults.length; i++) { + if (testResults[i].status !== testResults[i - 1].status) { + statusChanges++ + } + } + acc[result.name] = (statusChanges / testResults.length) * 100 + return acc + }, + {} + ) + + const flakyTests = Object.entries(flakinessScores) + .filter(([, score]) => score > 20) + .sort(([, a], [, b]) => b - a) + .slice(0, 5) + + return ( +
+
+

Test Results Over Time

+
+ {chartData.map((point) => ( +
+ {point.date} +
+
+
+ {point.passed} +
+
+
+ {point.failed} +
+
+
+ {point.skipped} +
+
+ + {(point.totalDuration / 1000).toFixed(2)}s + +
+ ))} +
+
+ +
+

Slowest Tests

+
+ {slowestTests.map((test) => ( +
+ {test.name} + + {(test.duration / 1000).toFixed(2)}s + +
+ ))} +
+
+ + {flakyTests.length > 0 && ( +
+

Flaky Tests

+
+ {flakyTests.map(([name, score]) => ( +
+ {name} + + {score.toFixed(1)}% flaky + +
+ ))} +
+
+ )} + +
+

Branch Statistics

+
+ {Object.entries(branchStats).map(([branch, stats]) => ( +
0 + ? 'border-ds-primary-red bg-ds-pink-default' + : 'border-ds-gray-tertiary bg-white' + )} + > +

{branch}

+
+
Total: {stats.total}
+
+ Failed: {stats.failed} +
+
Avg: {stats.avgDuration.toFixed(0)}ms
+
+
+ ))} +
+
+
+ ) +} + +export default TestResultsChart From a1992387679e66a2cdd34baf186bcb478cabd8a7 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Thu, 2 Oct 2025 14:49:47 -0700 Subject: [PATCH 2/6] comments --- .../TestResultsChart/TestResultsChart.tsx | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx index 7b36ec247b..32f76d37a5 100644 --- a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx +++ b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx @@ -26,7 +26,6 @@ export function TestResultsChart({ results }: TestResultsChartProps) { return null } - // Complex calculation: Transform test results into chart data by date const chartData = results.reduce((acc: ChartDataPoint[], result) => { const date = new Date(result.timestamp).toLocaleDateString() const existing = acc.find((item) => item.date === date) @@ -47,13 +46,11 @@ export function TestResultsChart({ results }: TestResultsChartProps) { return acc }, []) - // Complex calculation: Find top 10 slowest tests const slowestTests = results .filter((result) => result.status !== 'skipped') .sort((a, b) => b.duration - a.duration) .slice(0, 10) - // Complex calculation: Calculate statistics per branch const branchStats = results.reduce( ( acc: Record< @@ -65,30 +62,32 @@ export function TestResultsChart({ results }: TestResultsChartProps) { if (!acc[result.branch]) { acc[result.branch] = { total: 0, failed: 0, avgDuration: 0 } } - acc[result.branch].total++ - if (result.status === 'failed') { - acc[result.branch].failed++ + const branchData = acc[result.branch] + if (branchData) { + branchData.total++ + if (result.status === 'failed') { + branchData.failed++ + } + branchData.avgDuration = + (branchData.avgDuration * (branchData.total - 1) + result.duration) / + branchData.total } - acc[result.branch].avgDuration = - (acc[result.branch].avgDuration * (acc[result.branch].total - 1) + - result.duration) / - acc[result.branch].total return acc }, {} ) - // Complex calculation: Calculate flakiness score for each test const flakinessScores = results.reduce( (acc: Record, result) => { if (!acc[result.name]) { acc[result.name] = 0 } - // Count status changes as flakiness indicator const testResults = results.filter((r) => r.name === result.name) let statusChanges = 0 for (let i = 1; i < testResults.length; i++) { - if (testResults[i].status !== testResults[i - 1].status) { + const current = testResults.at(i) + const previous = testResults.at(i - 1) + if (current && previous && current.status !== previous.status) { statusChanges++ } } From b2da1e80d6bac81242f168d184f58bd032424b7a Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Fri, 3 Oct 2025 13:38:41 -0700 Subject: [PATCH 3/6] commit --- .../components/TestResultsChart/TestResultsChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx index 32f76d37a5..1fdf23afc7 100644 --- a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx +++ b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx @@ -36,7 +36,7 @@ export function TestResultsChart({ results }: TestResultsChartProps) { } else { acc.push({ date, - passed: result.status === 'passed' ? 1 : 0, + passed: result.status === 'passed' ? 2 : 0, failed: result.status === 'failed' ? 1 : 0, skipped: result.status === 'skipped' ? 1 : 0, totalDuration: result.duration, From a64b2ab1d3ecbea94751caa491c5eb1ee9c092c3 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Fri, 3 Oct 2025 15:12:22 -0700 Subject: [PATCH 4/6] swap back --- .../components/TestResultsChart/TestResultsChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx index 1fdf23afc7..32f76d37a5 100644 --- a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx +++ b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx @@ -36,7 +36,7 @@ export function TestResultsChart({ results }: TestResultsChartProps) { } else { acc.push({ date, - passed: result.status === 'passed' ? 2 : 0, + passed: result.status === 'passed' ? 1 : 0, failed: result.status === 'failed' ? 1 : 0, skipped: result.status === 'skipped' ? 1 : 0, totalDuration: result.duration, From 43c40a1a5258aab683a9aec05110b3dfee4392a0 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Thu, 9 Oct 2025 10:43:59 -0700 Subject: [PATCH 5/6] fix some stuff --- .../TestResultsChart/TestResultsChart.tsx | 77 +++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx index 32f76d37a5..1d31dbf97c 100644 --- a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx +++ b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx @@ -26,25 +26,32 @@ export function TestResultsChart({ results }: TestResultsChartProps) { return null } - const chartData = results.reduce((acc: ChartDataPoint[], result) => { - const date = new Date(result.timestamp).toLocaleDateString() - const existing = acc.find((item) => item.date === date) - - if (existing) { - existing[result.status]++ - existing.totalDuration += result.duration - } else { - acc.push({ - date, - passed: result.status === 'passed' ? 1 : 0, - failed: result.status === 'failed' ? 1 : 0, - skipped: result.status === 'skipped' ? 1 : 0, - totalDuration: result.duration, - }) - } + const chartDataMap = results.reduce( + (acc: Record, result) => { + const date = new Date(result.timestamp).toLocaleDateString() + + if (!acc[date]) { + acc[date] = { + date, + passed: 0, + failed: 0, + skipped: 0, + totalDuration: 0, + } + } - return acc - }, []) + const dataPoint = acc[date] + if (dataPoint) { + dataPoint[result.status]++ + dataPoint.totalDuration += result.duration + } + + return acc + }, + {} + ) + + const chartData = Object.values(chartDataMap) const slowestTests = results .filter((result) => result.status !== 'skipped') @@ -77,26 +84,34 @@ export function TestResultsChart({ results }: TestResultsChartProps) { {} ) - const flakinessScores = results.reduce( - (acc: Record, result) => { + // Group results by test name and calculate flakiness + const testsByName = results.reduce( + (acc: Record, result) => { if (!acc[result.name]) { - acc[result.name] = 0 - } - const testResults = results.filter((r) => r.name === result.name) - let statusChanges = 0 - for (let i = 1; i < testResults.length; i++) { - const current = testResults.at(i) - const previous = testResults.at(i - 1) - if (current && previous && current.status !== previous.status) { - statusChanges++ - } + acc[result.name] = [] } - acc[result.name] = (statusChanges / testResults.length) * 100 + acc[result.name]?.push(result) return acc }, {} ) + const flakinessScores: Record = {} + Object.entries(testsByName).forEach(([testName, testResults]) => { + // Sort by timestamp to ensure chronological order + const sortedResults = testResults.sort((a, b) => a.timestamp - b.timestamp) + + let statusChanges = 0 + for (let i = 1; i < sortedResults.length; i++) { + const current = sortedResults[i] + const previous = sortedResults[i - 1] + if (current && previous && current.status !== previous.status) { + statusChanges++ + } + } + flakinessScores[testName] = (statusChanges / sortedResults.length) * 100 + }) + const flakyTests = Object.entries(flakinessScores) .filter(([, score]) => score > 20) .sort(([, a], [, b]) => b - a) From 82f09271ebd2e0d9359f73387a041f741827f5f0 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Thu, 9 Oct 2025 15:04:24 -0700 Subject: [PATCH 6/6] stuff --- .../TestResultsChart/TestResultsChart.tsx | 74 ++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx index 1d31dbf97c..03c044f45f 100644 --- a/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx +++ b/src/pages/AnalyticsPage/components/TestResultsChart/TestResultsChart.tsx @@ -28,7 +28,27 @@ export function TestResultsChart({ results }: TestResultsChartProps) { const chartDataMap = results.reduce( (acc: Record, result) => { - const date = new Date(result.timestamp).toLocaleDateString() + if ( + !result.timestamp || + !Number.isFinite(result.timestamp) || + result.timestamp < 0 + ) { + return acc + } + + const dateObj = new Date(result.timestamp) + if (isNaN(dateObj.getTime())) { + return acc + } + + if ( + !result.status || + !['passed', 'failed', 'skipped'].includes(result.status) + ) { + return acc + } + + const date = dateObj.toLocaleDateString() if (!acc[date]) { acc[date] = { @@ -43,7 +63,11 @@ export function TestResultsChart({ results }: TestResultsChartProps) { const dataPoint = acc[date] if (dataPoint) { dataPoint[result.status]++ - dataPoint.totalDuration += result.duration + const duration = + Number.isFinite(result.duration) && result.duration >= 0 + ? result.duration + : 0 + dataPoint.totalDuration += duration } return acc @@ -55,6 +79,9 @@ export function TestResultsChart({ results }: TestResultsChartProps) { const slowestTests = results .filter((result) => result.status !== 'skipped') + .filter( + (result) => Number.isFinite(result.duration) && result.duration >= 0 + ) .sort((a, b) => b.duration - a.duration) .slice(0, 10) @@ -66,6 +93,10 @@ export function TestResultsChart({ results }: TestResultsChartProps) { >, result ) => { + if (!result.branch) { + return acc + } + if (!acc[result.branch]) { acc[result.branch] = { total: 0, failed: 0, avgDuration: 0 } } @@ -75,8 +106,13 @@ export function TestResultsChart({ results }: TestResultsChartProps) { if (result.status === 'failed') { branchData.failed++ } + + const duration = + Number.isFinite(result.duration) && result.duration >= 0 + ? result.duration + : 0 branchData.avgDuration = - (branchData.avgDuration * (branchData.total - 1) + result.duration) / + (branchData.avgDuration * (branchData.total - 1) + duration) / branchData.total } return acc @@ -84,9 +120,12 @@ export function TestResultsChart({ results }: TestResultsChartProps) { {} ) - // Group results by test name and calculate flakiness const testsByName = results.reduce( (acc: Record, result) => { + if (!result.name) { + return acc + } + if (!acc[result.name]) { acc[result.name] = [] } @@ -98,8 +137,13 @@ export function TestResultsChart({ results }: TestResultsChartProps) { const flakinessScores: Record = {} Object.entries(testsByName).forEach(([testName, testResults]) => { - // Sort by timestamp to ensure chronological order - const sortedResults = testResults.sort((a, b) => a.timestamp - b.timestamp) + if (testResults.length === 0) { + return + } + + const sortedResults = [...testResults].sort( + (a, b) => a.timestamp - b.timestamp + ) let statusChanges = 0 for (let i = 1; i < sortedResults.length; i++) { @@ -143,7 +187,10 @@ export function TestResultsChart({ results }: TestResultsChartProps) {
- {(point.totalDuration / 1000).toFixed(2)}s + {Number.isFinite(point.totalDuration) + ? (point.totalDuration / 1000).toFixed(2) + : '0.00'} + s
))} @@ -160,7 +207,10 @@ export function TestResultsChart({ results }: TestResultsChartProps) { > {test.name} - {(test.duration / 1000).toFixed(2)}s + {Number.isFinite(test.duration) + ? (test.duration / 1000).toFixed(2) + : '0.00'} + s ))} @@ -205,7 +255,13 @@ export function TestResultsChart({ results }: TestResultsChartProps) {
Failed: {stats.failed}
-
Avg: {stats.avgDuration.toFixed(0)}ms
+
+ Avg:{' '} + {Number.isFinite(stats.avgDuration) + ? stats.avgDuration.toFixed(0) + : '0'} + ms +
))}