From ee78bae7b4410cc5231ee4589ec01db79023e605 Mon Sep 17 00:00:00 2001 From: Lupita Bot <263059171+lupita-hom@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:03:10 -0600 Subject: [PATCH 1/2] fix: dashboard metrics and stats view bugs (#21) - Fix Weekly Activity Trend using Math.random() instead of actual data - Remove local formatDuration that shadowed the correct imported version (was treating ms values as seconds, showing wrong durations) - Add avgDuration to backend repo stats aggregation pipeline - Fix division-by-zero crashes when totalRuns is 0 - Render the Weekly Activity Trend chart (was computed but never shown) - Expand recentRuns query to 7 days for meaningful trend data - Layout success rates + trend side-by-side for better use of space Fixes #21 --- client/src/features/stats/RepositoryStats.jsx | 105 ++++++++++++------ server/src/services/workflowService.js | 13 ++- 2 files changed, 85 insertions(+), 33 deletions(-) diff --git a/client/src/features/stats/RepositoryStats.jsx b/client/src/features/stats/RepositoryStats.jsx index 6fa34a0..f6e3ab8 100644 --- a/client/src/features/stats/RepositoryStats.jsx +++ b/client/src/features/stats/RepositoryStats.jsx @@ -33,6 +33,7 @@ import { } from 'chart.js'; import { Bar, Pie, Line } from 'react-chartjs-2'; import apiService from '../../api/apiService'; +import { formatDuration } from '../../common/utils/statusHelpers'; import { useNavigate } from 'react-router-dom'; // Register ChartJS components @@ -100,7 +101,7 @@ const RepositoryStats = () => { const successData = orgLabels.map(org => orgStats[org].successfulRuns); const failureData = orgLabels.map(org => orgStats[org].failedRuns); const successRates = orgLabels.map(org => - (orgStats[org].successfulRuns / orgStats[org].totalRuns * 100).toFixed(1) + orgStats[org].totalRuns ? (orgStats[org].successfulRuns / orgStats[org].totalRuns * 100).toFixed(1) : 0 ); return { @@ -224,27 +225,39 @@ const RepositoryStats = () => { ], }; - // Recent activity trend simulation - const today = new Date(); - const timeLabels = Array.from({ length: 7 }, (_, i) => { - const d = new Date(); - d.setDate(today.getDate() - (6 - i)); - return d.toLocaleDateString('en-US', { weekday: 'short' }); - }); + // Recent activity trend — uses actual run dates from recentRuns + const trendData = React.useMemo(() => { + const today = new Date(); + const days = Array.from({ length: 7 }, (_, i) => { + const d = new Date(); + d.setDate(today.getDate() - (6 - i)); + return d.toISOString().split('T')[0]; + }); - const trendData = { - labels: timeLabels, - datasets: [ - { - label: 'Workflow Activity', - data: timeLabels.map(() => Math.floor(Math.random() * (stats.totalRuns / 7)) + 1), - borderColor: 'rgba(88, 166, 255, 1)', - backgroundColor: 'rgba(88, 166, 255, 0.5)', - tension: 0.4, - fill: true, - } - ], - }; + const timeLabels = days.map(date => + new Date(date).toLocaleDateString('en-US', { weekday: 'short' }) + ); + + // Count runs per day from recentRuns (if available) — otherwise zeros + const recentRuns = stats.recentRuns || []; + const counts = days.map(date => + recentRuns.filter(r => r.run?.created_at?.startsWith(date)).length + ); + + return { + labels: timeLabels, + datasets: [ + { + label: 'Workflow Activity', + data: counts, + borderColor: 'rgba(88, 166, 255, 1)', + backgroundColor: 'rgba(88, 166, 255, 0.5)', + tension: 0.4, + fill: true, + } + ], + }; + }, [stats.recentRuns]); return ( @@ -362,7 +375,7 @@ const RepositoryStats = () => { {/* Success Rates by Organization */} - + { + {/* Weekly Activity Trend */} + + + + Weekly Activity Trend + + + + + + + {/* Organization Details */} {stats.orgStats && Object.entries(stats.orgStats).map(([orgName, orgData]) => ( @@ -433,7 +483,7 @@ const RepositoryStats = () => { Success Rate - {(orgData.successfulRuns / orgData.totalRuns * 100).toFixed(1)}% + {orgData.totalRuns ? (orgData.successfulRuns / orgData.totalRuns * 100).toFixed(1) : 0}% @@ -509,7 +559,7 @@ const RepositoryStats = () => { Success Rate - {(repo.successfulRuns / repo.totalRuns * 100).toFixed(1)}% + {repo.totalRuns ? (repo.successfulRuns / repo.totalRuns * 100).toFixed(1) : 0}% @@ -537,11 +587,4 @@ const RepositoryStats = () => { ); }; -const formatDuration = (duration) => { - if (!duration) return 'N/A'; - const minutes = Math.floor(duration / 60); - const seconds = duration % 60; - return `${minutes}m ${seconds}s`; -}; - export default RepositoryStats; \ No newline at end of file diff --git a/server/src/services/workflowService.js b/server/src/services/workflowService.js index b36ca96..d613d24 100644 --- a/server/src/services/workflowService.js +++ b/server/src/services/workflowService.js @@ -136,9 +136,10 @@ export const calculateWorkflowStats = async () => { WorkflowRun.countDocuments({ 'run.conclusion': 'success' }), WorkflowRun.countDocuments({ 'run.conclusion': 'failure' }), WorkflowRun.countDocuments({ 'run.status': 'in_progress' }), - WorkflowRun.find() + WorkflowRun.find({ + 'run.created_at': { $gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString() } + }) .sort({ 'run.created_at': -1 }) - .limit(5) .select('repository workflow run') ]); @@ -157,6 +158,14 @@ export const calculateWorkflowStats = async () => { $sum: { $cond: [{ $eq: ['$run.conclusion', 'failure'] }, 1, 0] } + }, + avgDuration: { + $avg: { + $subtract: [ + { $toDate: '$run.updated_at' }, + { $toDate: '$run.created_at' } + ] + } } } }, From 2b6ce46e935a6ac769dcb0bf0134893f77fdd89f Mon Sep 17 00:00:00 2001 From: Lupita Bot <263059171+lupita-hom@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:21:33 -0600 Subject: [PATCH 2/2] fix: move useMemo before early returns to fix hooks rule --- client/src/features/stats/RepositoryStats.jsx | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/client/src/features/stats/RepositoryStats.jsx b/client/src/features/stats/RepositoryStats.jsx index f6e3ab8..15ec6b7 100644 --- a/client/src/features/stats/RepositoryStats.jsx +++ b/client/src/features/stats/RepositoryStats.jsx @@ -156,6 +156,40 @@ const RepositoryStats = () => { fetchStats(); }, []); + // Recent activity trend — uses actual run dates from recentRuns + const trendData = React.useMemo(() => { + if (!stats?.recentRuns) return { labels: [], datasets: [] }; + const today = new Date(); + const days = Array.from({ length: 7 }, (_, i) => { + const d = new Date(); + d.setDate(today.getDate() - (6 - i)); + return d.toISOString().split('T')[0]; + }); + + const timeLabels = days.map(date => + new Date(date).toLocaleDateString('en-US', { weekday: 'short' }) + ); + + const recentRuns = stats.recentRuns || []; + const counts = days.map(date => + recentRuns.filter(r => r.run?.created_at?.startsWith(date)).length + ); + + return { + labels: timeLabels, + datasets: [ + { + label: 'Workflow Activity', + data: counts, + borderColor: 'rgba(88, 166, 255, 1)', + backgroundColor: 'rgba(88, 166, 255, 0.5)', + tension: 0.4, + fill: true, + } + ], + }; + }, [stats]); + if (loading) { return ( @@ -225,40 +259,6 @@ const RepositoryStats = () => { ], }; - // Recent activity trend — uses actual run dates from recentRuns - const trendData = React.useMemo(() => { - const today = new Date(); - const days = Array.from({ length: 7 }, (_, i) => { - const d = new Date(); - d.setDate(today.getDate() - (6 - i)); - return d.toISOString().split('T')[0]; - }); - - const timeLabels = days.map(date => - new Date(date).toLocaleDateString('en-US', { weekday: 'short' }) - ); - - // Count runs per day from recentRuns (if available) — otherwise zeros - const recentRuns = stats.recentRuns || []; - const counts = days.map(date => - recentRuns.filter(r => r.run?.created_at?.startsWith(date)).length - ); - - return { - labels: timeLabels, - datasets: [ - { - label: 'Workflow Activity', - data: counts, - borderColor: 'rgba(88, 166, 255, 1)', - backgroundColor: 'rgba(88, 166, 255, 0.5)', - tension: 0.4, - fill: true, - } - ], - }; - }, [stats.recentRuns]); - return (