Fix update bundle

This commit is contained in:
2025-11-11 05:47:57 +07:00
parent 0a676d1fb7
commit 14bc98d52d
4 changed files with 142 additions and 31 deletions

View File

@@ -642,6 +642,11 @@ public class BacktestExecutor
var runningJobs = jobs.Count(j => j.Status == JobStatus.Running); var runningJobs = jobs.Count(j => j.Status == JobStatus.Running);
var totalJobs = jobs.Count(); 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), // 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 // don't overwrite it unless we're detecting a legitimate change
if (bundleRequest.CompletedAt.HasValue && if (bundleRequest.CompletedAt.HasValue &&
@@ -655,7 +660,8 @@ public class BacktestExecutor
_logger.LogDebug( _logger.LogDebug(
"Bundle {BundleRequestId} already completed/failed. Skipping status update.", "Bundle {BundleRequestId} already completed/failed. Skipping status update.",
bundleRequestId); bundleRequestId);
return; // Don't modify a completed bundle // Progress counters already updated above, just return
return;
} }
else else
{ {
@@ -666,10 +672,6 @@ public class BacktestExecutor
} }
} }
// Update bundle request progress
bundleRequest.CompletedBacktests = completedJobs;
bundleRequest.FailedBacktests = failedJobs;
// Update status based on job states // Update status based on job states
if (completedJobs + failedJobs == totalJobs) if (completedJobs + failedJobs == totalJobs)
{ {

View File

@@ -458,6 +458,11 @@ public class BacktestComputeWorker : BackgroundService
var previousStatus = bundleRequest.Status; 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), // 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 // don't overwrite it unless we're detecting a legitimate change
if (bundleRequest.CompletedAt.HasValue && if (bundleRequest.CompletedAt.HasValue &&
@@ -471,7 +476,8 @@ public class BacktestComputeWorker : BackgroundService
_logger.LogDebug( _logger.LogDebug(
"Bundle {BundleRequestId} already completed/failed. Skipping status update.", "Bundle {BundleRequestId} already completed/failed. Skipping status update.",
bundleRequestId); bundleRequestId);
return; // Don't modify a completed bundle // Progress counters already updated above, just return
return;
} }
else 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 // Update status based on job states
if (completedJobs + failedJobs == totalJobs) if (completedJobs + failedJobs == totalJobs)
{ {

View File

@@ -5,10 +5,13 @@ import useApiUrlStore from '../../../app/store/apiStore'
import { import {
AdminClient, AdminClient,
BundleBacktestRequestSortableColumn, BundleBacktestRequestSortableColumn,
BundleBacktestRequestStatus BundleBacktestRequestStatus,
JobClient
} from '../../../generated/ManagingApi' } from '../../../generated/ManagingApi'
import {Modal} from '../../../components/mollecules'
import BundleBacktestRequestsTable from './bundleBacktestRequestsTable' import BundleBacktestRequestsTable from './bundleBacktestRequestsTable'
import JobsTable from '../jobs/jobsTable'
const BundleBacktestRequestsSettings: React.FC = () => { const BundleBacktestRequestsSettings: React.FC = () => {
const { apiUrl } = useApiUrlStore() const { apiUrl } = useApiUrlStore()
@@ -29,8 +32,15 @@ const BundleBacktestRequestsSettings: React.FC = () => {
const [filtersOpen, setFiltersOpen] = useState<boolean>(false) const [filtersOpen, setFiltersOpen] = useState<boolean>(false)
const [showTable, setShowTable] = useState<boolean>(true) const [showTable, setShowTable] = useState<boolean>(true)
const [deleteConfirmRequestId, setDeleteConfirmRequestId] = useState<string | null>(null) const [deleteConfirmRequestId, setDeleteConfirmRequestId] = useState<string | null>(null)
const [viewJobsModalOpen, setViewJobsModalOpen] = useState<boolean>(false)
const [selectedBundleRequestId, setSelectedBundleRequestId] = useState<string | null>(null)
const [jobsPage, setJobsPage] = useState(1)
const [jobsPageSize] = useState(50)
const [jobsSortBy, setJobsSortBy] = useState<string>('CreatedAt')
const [jobsSortOrder, setJobsSortOrder] = useState<string>('desc')
const adminClient = new AdminClient({}, apiUrl) const adminClient = new AdminClient({}, apiUrl)
const jobClient = new JobClient({}, apiUrl)
const queryClient = useQueryClient() const queryClient = useQueryClient()
// Fetch bundle backtest requests summary statistics // Fetch bundle backtest requests summary statistics
@@ -85,6 +95,36 @@ const BundleBacktestRequestsSettings: React.FC = () => {
const totalPages = bundleRequestsData?.totalPages || 0 const totalPages = bundleRequestsData?.totalPages || 0
const currentPage = bundleRequestsData?.currentPage || 1 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 // Delete mutation
const deleteMutation = useMutation({ const deleteMutation = useMutation({
mutationFn: async (requestId: string) => { 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) => { const handlePageChange = (newPage: number) => {
setPage(newPage) setPage(newPage)
} }
@@ -549,6 +613,7 @@ const BundleBacktestRequestsSettings: React.FC = () => {
onPageChange={handlePageChange} onPageChange={handlePageChange}
onSortChange={handleSortChange} onSortChange={handleSortChange}
onDelete={handleDelete} onDelete={handleDelete}
onViewJobs={handleViewJobs}
/> />
{error && ( {error && (
@@ -584,6 +649,29 @@ const BundleBacktestRequestsSettings: React.FC = () => {
</div> </div>
</div> </div>
)} )}
{/* View Jobs Modal */}
<Modal
showModal={viewJobsModalOpen}
onClose={handleCloseJobsModal}
titleHeader={`Jobs for Bundle Request: ${selectedBundleRequestId?.substring(0, 8)}...`}
size="xl"
>
<div className="max-h-[70vh] overflow-y-auto">
<JobsTable
jobs={jobs}
isLoading={isLoadingJobs}
totalCount={jobsTotalCount}
currentPage={jobsCurrentPage}
totalPages={jobsTotalPages}
pageSize={jobsPageSize}
sortBy={jobsSortBy}
sortOrder={jobsSortOrder}
onPageChange={handleJobsPageChange}
onSortChange={handleJobsSortChange}
/>
</div>
</Modal>
</div> </div>
) )
} }

View File

@@ -17,6 +17,7 @@ interface IBundleBacktestRequestsTable {
onPageChange: (page: number) => void onPageChange: (page: number) => void
onSortChange: (sortBy: BundleBacktestRequestSortableColumn) => void onSortChange: (sortBy: BundleBacktestRequestSortableColumn) => void
onDelete?: (requestId: string) => void onDelete?: (requestId: string) => void
onViewJobs?: (bundleRequestId: string) => void
} }
const BundleBacktestRequestsTable: React.FC<IBundleBacktestRequestsTable> = ({ const BundleBacktestRequestsTable: React.FC<IBundleBacktestRequestsTable> = ({
@@ -30,7 +31,8 @@ const BundleBacktestRequestsTable: React.FC<IBundleBacktestRequestsTable> = ({
sortOrder, sortOrder,
onPageChange, onPageChange,
onSortChange, onSortChange,
onDelete onDelete,
onViewJobs
}) => { }) => {
const getStatusBadge = (status: string | null | undefined) => { const getStatusBadge = (status: string | null | undefined) => {
if (!status) return <span className="badge badge-sm">-</span> if (!status) return <span className="badge badge-sm">-</span>
@@ -193,18 +195,35 @@ const BundleBacktestRequestsTable: React.FC<IBundleBacktestRequestsTable> = ({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-mono text-xs">{row.requestId?.substring(0, 8)}...</span> <span className="font-mono text-xs">{row.requestId?.substring(0, 8)}...</span>
{row.requestId && ( {row.requestId && (
<button <>
className="btn btn-ghost btn-xs p-1 h-auto min-h-0" <button
onClick={(e) => { className="btn btn-ghost btn-xs p-1 h-auto min-h-0"
e.stopPropagation() onClick={(e) => {
copyToClipboard(row.requestId || '') e.stopPropagation()
}} copyToClipboard(row.requestId || '')
title="Copy Request ID" }}
> title="Copy Request ID"
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-4 h-4"> >
<path strokeLinecap="round" strokeLinejoin="round" d="M15.666 3.6A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.6m5.332 0A2.25 2.25 0 0115.75 4.5v3.75m0 0v3.75m0-3.75h3.75m-3.75 0h-3.75M15 15.75a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15.75V8.25a2.25 2.25 0 012.25-2.25h7.5A2.25 2.25 0 0115 8.25v7.5z" /> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-4 h-4">
</svg> <path strokeLinecap="round" strokeLinejoin="round" d="M15.666 3.6A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.6m5.332 0A2.25 2.25 0 0115.75 4.5v3.75m0 0v3.75m0-3.75h3.75m-3.75 0h-3.75M15 15.75a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15.75V8.25a2.25 2.25 0 012.25-2.25h7.5A2.25 2.25 0 0115 8.25v7.5z" />
</button> </svg>
</button>
{onViewJobs && (
<button
className="btn btn-ghost btn-xs p-1 h-auto min-h-0"
onClick={(e) => {
e.stopPropagation()
onViewJobs(row.requestId || '')
}}
title="View Jobs"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-4 h-4">
<path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</button>
)}
</>
)} )}
</div> </div>
) )
@@ -225,7 +244,7 @@ const BundleBacktestRequestsTable: React.FC<IBundleBacktestRequestsTable> = ({
</button> </button>
) )
}] : []) }] : [])
], [sortBy, sortOrder, onSortChange, onDelete]) ], [sortBy, sortOrder, onSortChange, onDelete, onViewJobs])
const tableData = useMemo(() => { const tableData = useMemo(() => {
return bundleRequests.map((request) => ({ return bundleRequests.map((request) => ({