diff --git a/src/Managing.Application/Backtests/BacktestExecutor.cs b/src/Managing.Application/Backtests/BacktestExecutor.cs index 0502fddf..6c79da72 100644 --- a/src/Managing.Application/Backtests/BacktestExecutor.cs +++ b/src/Managing.Application/Backtests/BacktestExecutor.cs @@ -641,11 +641,16 @@ public class BacktestExecutor var failedJobs = jobs.Count(j => j.Status == JobStatus.Failed); var runningJobs = jobs.Count(j => j.Status == JobStatus.Running); var totalJobs = jobs.Count(); - + + // Update bundle request progress (always update counters regardless of status) + bundleRequest.CompletedBacktests = completedJobs; + bundleRequest.FailedBacktests = failedJobs; + bundleRequest.UpdatedAt = DateTime.UtcNow; + // CRITICAL: If bundle is already in a final state (Completed/Failed with CompletedAt set), // don't overwrite it unless we're detecting a legitimate change - if (bundleRequest.CompletedAt.HasValue && - (bundleRequest.Status == BundleBacktestRequestStatus.Completed || + if (bundleRequest.CompletedAt.HasValue && + (bundleRequest.Status == BundleBacktestRequestStatus.Completed || bundleRequest.Status == BundleBacktestRequestStatus.Failed)) { // Bundle already finalized, only update if job counts indicate it should be re-opened @@ -655,7 +660,8 @@ public class BacktestExecutor _logger.LogDebug( "Bundle {BundleRequestId} already completed/failed. Skipping status update.", bundleRequestId); - return; // Don't modify a completed bundle + // Progress counters already updated above, just return + return; } else { @@ -666,10 +672,6 @@ public class BacktestExecutor } } - // Update bundle request progress - bundleRequest.CompletedBacktests = completedJobs; - bundleRequest.FailedBacktests = failedJobs; - // Update status based on job states if (completedJobs + failedJobs == totalJobs) { diff --git a/src/Managing.Application/Workers/BacktestComputeWorker.cs b/src/Managing.Application/Workers/BacktestComputeWorker.cs index 57e61b22..c9f5d5a2 100644 --- a/src/Managing.Application/Workers/BacktestComputeWorker.cs +++ b/src/Managing.Application/Workers/BacktestComputeWorker.cs @@ -457,11 +457,16 @@ public class BacktestComputeWorker : BackgroundService } var previousStatus = bundleRequest.Status; - + + // Update bundle request progress (always update counters regardless of status) + bundleRequest.CompletedBacktests = completedJobs; + bundleRequest.FailedBacktests = failedJobs; + bundleRequest.UpdatedAt = DateTime.UtcNow; + // CRITICAL: If bundle is already in a final state (Completed/Failed with CompletedAt set), // don't overwrite it unless we're detecting a legitimate change - if (bundleRequest.CompletedAt.HasValue && - (bundleRequest.Status == BundleBacktestRequestStatus.Completed || + if (bundleRequest.CompletedAt.HasValue && + (bundleRequest.Status == BundleBacktestRequestStatus.Completed || bundleRequest.Status == BundleBacktestRequestStatus.Failed)) { // Bundle already finalized, only update if job counts indicate it should be re-opened @@ -471,7 +476,8 @@ public class BacktestComputeWorker : BackgroundService _logger.LogDebug( "Bundle {BundleRequestId} already completed/failed. Skipping status update.", bundleRequestId); - return; // Don't modify a completed bundle + // Progress counters already updated above, just return + return; } else { @@ -482,10 +488,6 @@ public class BacktestComputeWorker : BackgroundService } } - // Update bundle request progress - bundleRequest.CompletedBacktests = completedJobs; - bundleRequest.FailedBacktests = failedJobs; - // Update status based on job states if (completedJobs + failedJobs == totalJobs) { diff --git a/src/Managing.WebApp/src/pages/adminPage/bundleBacktestRequests/bundleBacktestRequestsSettings.tsx b/src/Managing.WebApp/src/pages/adminPage/bundleBacktestRequests/bundleBacktestRequestsSettings.tsx index ba0410d8..2fa9a1fd 100644 --- a/src/Managing.WebApp/src/pages/adminPage/bundleBacktestRequests/bundleBacktestRequestsSettings.tsx +++ b/src/Managing.WebApp/src/pages/adminPage/bundleBacktestRequests/bundleBacktestRequestsSettings.tsx @@ -5,10 +5,13 @@ import useApiUrlStore from '../../../app/store/apiStore' import { AdminClient, BundleBacktestRequestSortableColumn, - BundleBacktestRequestStatus + BundleBacktestRequestStatus, + JobClient } from '../../../generated/ManagingApi' +import {Modal} from '../../../components/mollecules' import BundleBacktestRequestsTable from './bundleBacktestRequestsTable' +import JobsTable from '../jobs/jobsTable' const BundleBacktestRequestsSettings: React.FC = () => { const { apiUrl } = useApiUrlStore() @@ -29,8 +32,15 @@ const BundleBacktestRequestsSettings: React.FC = () => { const [filtersOpen, setFiltersOpen] = useState(false) const [showTable, setShowTable] = useState(true) const [deleteConfirmRequestId, setDeleteConfirmRequestId] = useState(null) + const [viewJobsModalOpen, setViewJobsModalOpen] = useState(false) + const [selectedBundleRequestId, setSelectedBundleRequestId] = useState(null) + const [jobsPage, setJobsPage] = useState(1) + const [jobsPageSize] = useState(50) + const [jobsSortBy, setJobsSortBy] = useState('CreatedAt') + const [jobsSortOrder, setJobsSortOrder] = useState('desc') const adminClient = new AdminClient({}, apiUrl) + const jobClient = new JobClient({}, apiUrl) const queryClient = useQueryClient() // Fetch bundle backtest requests summary statistics @@ -85,6 +95,36 @@ const BundleBacktestRequestsSettings: React.FC = () => { const totalPages = bundleRequestsData?.totalPages || 0 const currentPage = bundleRequestsData?.currentPage || 1 + // Fetch jobs for selected bundle request ID + const { + data: jobsData, + isLoading: isLoadingJobs + } = useQuery({ + queryKey: ['jobs', 'bundleRequest', selectedBundleRequestId, jobsPage, jobsPageSize, jobsSortBy, jobsSortOrder], + queryFn: async () => { + if (!selectedBundleRequestId) return null + return await jobClient.job_GetJobs( + jobsPage, + jobsPageSize, + jobsSortBy, + jobsSortOrder, + null, // status + null, // jobType + null, // userId + null, // workerId + selectedBundleRequestId // bundleRequestId + ) + }, + enabled: viewJobsModalOpen && !!selectedBundleRequestId, + staleTime: 10000, + gcTime: 5 * 60 * 1000, + }) + + const jobs = jobsData?.jobs || [] + const jobsTotalCount = jobsData?.totalCount || 0 + const jobsTotalPages = jobsData?.totalPages || 0 + const jobsCurrentPage = jobsData?.currentPage || 1 + // Delete mutation const deleteMutation = useMutation({ mutationFn: async (requestId: string) => { @@ -112,6 +152,30 @@ const BundleBacktestRequestsSettings: React.FC = () => { } } + const handleViewJobs = (bundleRequestId: string) => { + setSelectedBundleRequestId(bundleRequestId) + setViewJobsModalOpen(true) + setJobsPage(1) // Reset to first page + } + + const handleCloseJobsModal = () => { + setViewJobsModalOpen(false) + setSelectedBundleRequestId(null) + } + + const handleJobsPageChange = (newPage: number) => { + setJobsPage(newPage) + } + + const handleJobsSortChange = (newSortBy: string) => { + if (jobsSortBy === newSortBy) { + setJobsSortOrder(jobsSortOrder === 'asc' ? 'desc' : 'asc') + } else { + setJobsSortBy(newSortBy) + setJobsSortOrder('desc') + } + } + const handlePageChange = (newPage: number) => { setPage(newPage) } @@ -549,6 +613,7 @@ const BundleBacktestRequestsSettings: React.FC = () => { onPageChange={handlePageChange} onSortChange={handleSortChange} onDelete={handleDelete} + onViewJobs={handleViewJobs} /> {error && ( @@ -584,6 +649,29 @@ const BundleBacktestRequestsSettings: React.FC = () => { )} + + {/* View Jobs Modal */} + +
+ +
+
) } diff --git a/src/Managing.WebApp/src/pages/adminPage/bundleBacktestRequests/bundleBacktestRequestsTable.tsx b/src/Managing.WebApp/src/pages/adminPage/bundleBacktestRequests/bundleBacktestRequestsTable.tsx index 2fff7d02..d4a957f1 100644 --- a/src/Managing.WebApp/src/pages/adminPage/bundleBacktestRequests/bundleBacktestRequestsTable.tsx +++ b/src/Managing.WebApp/src/pages/adminPage/bundleBacktestRequests/bundleBacktestRequestsTable.tsx @@ -17,6 +17,7 @@ interface IBundleBacktestRequestsTable { onPageChange: (page: number) => void onSortChange: (sortBy: BundleBacktestRequestSortableColumn) => void onDelete?: (requestId: string) => void + onViewJobs?: (bundleRequestId: string) => void } const BundleBacktestRequestsTable: React.FC = ({ @@ -30,7 +31,8 @@ const BundleBacktestRequestsTable: React.FC = ({ sortOrder, onPageChange, onSortChange, - onDelete + onDelete, + onViewJobs }) => { const getStatusBadge = (status: string | null | undefined) => { if (!status) return - @@ -193,18 +195,35 @@ const BundleBacktestRequestsTable: React.FC = ({
{row.requestId?.substring(0, 8)}... {row.requestId && ( - + <> + + {onViewJobs && ( + + )} + )}
) @@ -225,7 +244,7 @@ const BundleBacktestRequestsTable: React.FC = ({ ) }] : []) - ], [sortBy, sortOrder, onSortChange, onDelete]) + ], [sortBy, sortOrder, onSortChange, onDelete, onViewJobs]) const tableData = useMemo(() => { return bundleRequests.map((request) => ({