diff --git a/src/components/jobs/Row.tsx b/src/components/jobs/Row.tsx index 7ab2b2e..bf38da1 100644 --- a/src/components/jobs/Row.tsx +++ b/src/components/jobs/Row.tsx @@ -7,6 +7,10 @@ import { CircularProgress, Collapse, IconButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, Snackbar, Table, TableBody, @@ -17,7 +21,7 @@ import { Typography, useTheme, } from '@mui/material'; -import { Job, MantidVersionMap } from '../../lib/types'; +import { Job, MantidVersionMap, outputFilter } from '../../lib/types'; import { CheckCircleOutline, Download, @@ -26,10 +30,12 @@ import { ImageAspectRatio, KeyboardArrowDown, KeyboardArrowUp, + MoreVert, People, Schedule, Schema, StackedBarChart, + Visibility, VpnKey, WarningAmber, WorkOutline, @@ -61,6 +67,9 @@ const openDataViewer = (jobId: number, instrumentName: string, experimentNumber: }); }; +const isH5Output = (output: string): boolean => + outputFilter.some((extension) => output.toLowerCase().endsWith(extension)); + const JobStatusIcon: React.FC<{ state: string }> = ({ state }: { state: string }): ReactElement => { const icons: Record = { ERROR: , @@ -77,92 +86,130 @@ const JobOutput: React.FC<{ downloadingSingle: string | null; handleDownload: (job: Job, output: string) => Promise; }> = ({ job, downloadingSingle, handleDownload }) => { - try { - if (typeof job.outputs !== 'string') { - return No outputs to show; + const parsedOutputs = parseJobOutputs(job.outputs); + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const [selectedOutput, setSelectedOutput] = useState(null); + + const instrumentName = job.run?.instrument_name || 'unknown'; + const experimentNumber = job.run?.experiment_number || 0; + const h5ViewerPath = `/reduction-history/${instrumentName}/experiment-viewer-${job.id}`; + + const handleMenuOpen = (event: React.MouseEvent, output: string): void => { + event.stopPropagation(); + setMenuAnchorEl(event.currentTarget); + setSelectedOutput(output); + }; + + const handleMenuClose = (): void => { + setMenuAnchorEl(null); + setSelectedOutput(null); + }; + + const handleView = (): void => { + if (!selectedOutput) { + return; } - let parsedOutputs; - if (job.outputs.startsWith('[') && job.outputs.endsWith(']')) { - parsedOutputs = JSON.parse(job.outputs.replace(/'/g, '"')); - } else { - parsedOutputs = [job.outputs]; + openDataViewer(job.id, instrumentName, experimentNumber, selectedOutput); + handleMenuClose(); + }; + + const handleMenuDownload = (): void => { + if (!selectedOutput) { + return; } - return parsedOutputs.map((output: string, index: number) => ( - + + const outputToDownload = selectedOutput; + handleMenuClose(); + void handleDownload(job, outputToDownload); + }; + + if (parsedOutputs.length === 0) { + return ( + - - + No outputs to show + + + ); + } + + return ( + <> + {parsedOutputs.map((output: string, index: number) => ( + + + {output} + + {downloadingSingle === output ? ( + + ) : ( + + handleMenuOpen(event, output)} + > + + + + )} + - - - {/* Show H5 Viewer button for HDF5 files */} - {(output.endsWith('.h5') || - output.endsWith('.hdf5') || - output.endsWith('.nxs') || - output.endsWith('.nxspe')) && ( - - )} - - - - - - )); - } catch (error) { - console.error('Failed to parse job outputs as JSON:', job.outputs); - console.error('Error:', error); - return {job.outputs}; - } + + + ))} + ) => event.stopPropagation()} + > + + + + + View + + + + + + H5 viewer + + + + + + Download + + + + ); }; const JobInput: React.FC<{ job: Job }> = ({ job }: { job: Job }): ReactElement => {