Fix update bundle
This commit is contained in:
@@ -641,11 +641,16 @@ public class BacktestExecutor
|
|||||||
var failedJobs = jobs.Count(j => j.Status == JobStatus.Failed);
|
var failedJobs = jobs.Count(j => j.Status == JobStatus.Failed);
|
||||||
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 &&
|
||||||
(bundleRequest.Status == BundleBacktestRequestStatus.Completed ||
|
(bundleRequest.Status == BundleBacktestRequestStatus.Completed ||
|
||||||
bundleRequest.Status == BundleBacktestRequestStatus.Failed))
|
bundleRequest.Status == BundleBacktestRequestStatus.Failed))
|
||||||
{
|
{
|
||||||
// Bundle already finalized, only update if job counts indicate it should be re-opened
|
// Bundle already finalized, only update if job counts indicate it should be re-opened
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -457,11 +457,16 @@ 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 &&
|
||||||
(bundleRequest.Status == BundleBacktestRequestStatus.Completed ||
|
(bundleRequest.Status == BundleBacktestRequestStatus.Completed ||
|
||||||
bundleRequest.Status == BundleBacktestRequestStatus.Failed))
|
bundleRequest.Status == BundleBacktestRequestStatus.Failed))
|
||||||
{
|
{
|
||||||
// Bundle already finalized, only update if job counts indicate it should be re-opened
|
// Bundle already finalized, only update if job counts indicate it should be re-opened
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user