Add job cancellation handling and periodic cleanup logic

Introduce new statuses, "to_cancel" and "cancelled", to improve job state tracking. Implement logic to nullify `slurm_id` for cancelled jobs and a background thread to clean up cancelled jobs older than 2 hours. Ensure periodic cleanup runs hourly to maintain database hygiene.
This commit is contained in:
GotthardG
2025-05-02 10:48:54 +02:00
parent a1b857b78a
commit b13a3e23f4
2 changed files with 106 additions and 40 deletions

View File

@ -1,9 +1,16 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { DataGridPremium, GridColDef } from '@mui/x-data-grid-premium';
import RunDetails from './RunDetails';
import './SampleImage.css';
import './ResultGrid.css';
import { OpenAPI, SamplesService } from '../../openapi';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import CancelIcon from '@mui/icons-material/Cancel';
import AutorenewIcon from '@mui/icons-material/Autorenew';
import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty';
import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline';
// import ErrorIcon from '@mui/icons-material/Error';
// import AccessTimeIcon from '@mui/icons-material/AccessTime';
// Extend your image info interface if needed.
@ -101,11 +108,57 @@ interface ResultGridProps {
activePgroup: string;
}
const useJobStream = (onJobs: (jobs: any[]) => void) => {
const eventSourceRef = useRef<EventSource | null>(null);
useEffect(() => {
eventSourceRef.current = new EventSource(`${OpenAPI.BASE}/processing/jobs/stream`);
eventSourceRef.current.onmessage = (event) => {
// Receives: data: [{job_id, run_id, status, ...}, ...]
const jobs = JSON.parse(event.data);
onJobs(jobs);
};
return () => {
if (eventSourceRef.current) {
eventSourceRef.current.close();
}
};
}, [onJobs]);
};
const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
const [rows, setRows] = useState<TreeRow[]>([]);
const [basePath, setBasePath] = useState('');
const [detailPanelHeights, setDetailPanelHeights] = useState<{ [key: string]: number }>({}); // Store dynamic heights
const [jobStatusMap, setJobStatusMap] = useState<{ [runId: number]: string }>({});
const getStatusIcon = (status: string) => {
switch (status) {
case 'todo':
return <HourglassEmptyIcon color="action" titleAccess="Todo" />;
case 'submitted':
return <AutorenewIcon color="primary" className="spin" titleAccess="Submitted" />;
case 'completed':
return <CheckCircleIcon color="success" titleAccess="Completed" />;
case 'failed':
return <CancelIcon color="error" titleAccess="Failed" />;
case 'no job':
default:
return <RemoveCircleOutlineIcon color="disabled" titleAccess="No job" />;
}
};
useJobStream((jobs) => {
const map: { [runId: number]: string } = {};
for (const job of jobs) {
// Map job status by run_id (or job_id as preferred)
map[job.run_id] = job.status;
}
setJobStatusMap(map);
});
const hasProcessingResults = (row: TreeRow): boolean => {
// You can later replace this placeholder with actual logic.
@ -238,6 +291,15 @@ const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
headerName: 'Sample Name',
width: 200,
},
{
field: 'jobStatus',
headerName: 'Job Status',
width: 120,
renderCell: (params) =>
params.row.type === 'run'
? getStatusIcon(jobStatusMap[params.row.experimentId] || 'no job')
: null,
},
{
field: 'puck_name',
headerName: 'Puck Name',