import { Box, Tabs, Typography, Tab, Paper, Button, CircularProgress, Accordion, AccordionSummary, AccordionDetails, ButtonGroup } from "@mui/material"; import Highlight from "react-highlight"; import * as React from "react"; import { Data } from "./RunsTable"; import { TabPanel, TabContext } from "@mui/lab"; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import { useEffect, useState } from "react"; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import 'highlight.js/styles/github.css'; import { useTranslation } from "react-i18next"; interface RunContentProps { row: Data, currentLog: string, interpretationInProgress: boolean, logEndRef: React.RefObject, abortRunHandler: () => void, } export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRef, abortRunHandler }: RunContentProps) => { const { t } = useTranslation(); const [tab, setTab] = React.useState('output'); const [schemaData, setSchemaData] = useState([]); const [schemaColumns, setSchemaColumns] = useState([]); const [listData, setListData] = useState([]); const [listColumns, setListColumns] = useState([]); const [currentListIndex, setCurrentListIndex] = useState(0); const [screenshotKeys, setScreenshotKeys] = useState([]); const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0); const [legacyData, setLegacyData] = useState([]); const [legacyColumns, setLegacyColumns] = useState([]); const [isLegacyData, setIsLegacyData] = useState(false); useEffect(() => { setTab(tab); }, [interpretationInProgress]); useEffect(() => { if (!row.serializableOutput) return; if (!row.serializableOutput.scrapeSchema && !row.serializableOutput.scrapeList && Object.keys(row.serializableOutput).length > 0) { setIsLegacyData(true); processLegacyData(row.serializableOutput); return; } setIsLegacyData(false); if (row.serializableOutput.scrapeSchema && Object.keys(row.serializableOutput.scrapeSchema).length > 0) { processDataCategory(row.serializableOutput.scrapeSchema, setSchemaData, setSchemaColumns); } if (row.serializableOutput.scrapeList) { processScrapeList(row.serializableOutput.scrapeList); } }, [row.serializableOutput]); useEffect(() => { if (row.binaryOutput && Object.keys(row.binaryOutput).length > 0) { setScreenshotKeys(Object.keys(row.binaryOutput)); setCurrentScreenshotIndex(0); } }, [row.binaryOutput]); const processLegacyData = (legacyOutput: Record) => { let allData: any[] = []; Object.keys(legacyOutput).forEach(key => { const data = legacyOutput[key]; if (Array.isArray(data)) { const filteredData = data.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); allData = [...allData, ...filteredData]; } }); if (allData.length > 0) { const allColumns = new Set(); allData.forEach(item => { Object.keys(item).forEach(key => allColumns.add(key)); }); setLegacyData(allData); setLegacyColumns(Array.from(allColumns)); } }; const processDataCategory = ( categoryData: Record, setData: React.Dispatch>, setColumns: React.Dispatch> ) => { let allData: any[] = []; Object.keys(categoryData).forEach(key => { const data = categoryData[key]; if (Array.isArray(data)) { const filteredData = data.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); allData = [...allData, ...filteredData]; } }); if (allData.length > 0) { const allColumns = new Set(); allData.forEach(item => { Object.keys(item).forEach(key => allColumns.add(key)); }); setData(allData); setColumns(Array.from(allColumns)); } }; const processScrapeList = (scrapeListData: any) => { const tablesList: any[][] = []; const columnsList: string[][] = []; if (Array.isArray(scrapeListData)) { scrapeListData.forEach(tableData => { if (Array.isArray(tableData) && tableData.length > 0) { const filteredData = tableData.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { tablesList.push(filteredData); const tableColumns = new Set(); filteredData.forEach(item => { Object.keys(item).forEach(key => tableColumns.add(key)); }); columnsList.push(Array.from(tableColumns)); } } }); } else if (typeof scrapeListData === 'object') { Object.keys(scrapeListData).forEach(key => { const tableData = scrapeListData[key]; if (Array.isArray(tableData) && tableData.length > 0) { const filteredData = tableData.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { tablesList.push(filteredData); const tableColumns = new Set(); filteredData.forEach(item => { Object.keys(item).forEach(key => tableColumns.add(key)); }); columnsList.push(Array.from(tableColumns)); } } }); } setListData(tablesList); setListColumns(columnsList); setCurrentListIndex(0); }; // Function to convert table data to CSV format const convertToCSV = (data: any[], columns: string[]): string => { const header = columns.join(','); const rows = data.map(row => columns.map(col => JSON.stringify(row[col] || "", null, 2)).join(',') ); return [header, ...rows].join('\n'); }; // Function to download a specific dataset as CSV const downloadCSV = (data: any[], columns: string[], filename: string) => { const csvContent = convertToCSV(data, columns); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.setAttribute("download", filename); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const downloadJSON = (data: any[], filename: string) => { const jsonContent = JSON.stringify(data, null, 2); const blob = new Blob([jsonContent], { type: 'application/json;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.setAttribute("download", filename); document.body.appendChild(link); link.click(); document.body.removeChild(link); setTimeout(() => { URL.revokeObjectURL(url); }, 100); }; const navigateListTable = (direction: 'next' | 'prev') => { if (direction === 'next' && currentListIndex < listData.length - 1) { setCurrentListIndex(currentListIndex + 1); } else if (direction === 'prev' && currentListIndex > 0) { setCurrentListIndex(currentListIndex - 1); } }; const navigateScreenshots = (direction: 'next' | 'prev') => { if (direction === 'next' && currentScreenshotIndex < screenshotKeys.length - 1) { setCurrentScreenshotIndex(currentScreenshotIndex + 1); } else if (direction === 'prev' && currentScreenshotIndex > 0) { setCurrentScreenshotIndex(currentScreenshotIndex - 1); } }; const renderDataTable = ( data: any[], columns: string[], title: string, csvFilename: string, jsonFilename: string, isPaginatedList: boolean = false ) => { if (!isPaginatedList && data.length === 0) return null; if (isPaginatedList && (listData.length === 0 || currentListIndex >= listData.length)) return null; const currentData = isPaginatedList ? listData[currentListIndex] : data; const currentColumns = isPaginatedList ? listColumns[currentListIndex] : columns; if (!currentData || currentData.length === 0) return null; return ( } aria-controls={`${title.toLowerCase()}-content`} id={`${title.toLowerCase()}-header`} > {title} {isPaginatedList && listData.length > 1 && ( )} {(isPaginatedList ? currentColumns : columns).map((column) => ( {column} ))} {(isPaginatedList ? currentData : data).map((row, index) => ( {(isPaginatedList ? currentColumns : columns).map((column) => ( {row[column] === undefined || row[column] === "" ? "-" : row[column]} ))} ))}
); }; const hasData = schemaData.length > 0 || listData.length > 0 || legacyData.length > 0; const hasScreenshots = row.binaryOutput && Object.keys(row.binaryOutput).length > 0; return ( setTab(newTab)} aria-label="run-content-tabs" sx={{ '& .MuiTabs-indicator': { backgroundColor: '#FF00C3', }, '& .MuiTab-root': { '&.Mui-selected': { color: '#FF00C3', }, } }} > theme.palette.mode === 'dark' ? '#fff' : '#000', '&:hover': { color: '#FF00C3' }, '&.Mui-selected': { color: '#FF00C3', } }} /> theme.palette.mode === 'dark' ? '#fff' : '#000', '&:hover': { color: '#FF00C3' }, '&.Mui-selected': { color: '#FF00C3', } }} />
{row.status === 'running' ? currentLog : row.log}
{row.status === 'running' ? : null} {row.status === 'running' || row.status === 'queued' ? ( {t('run_content.loading')} ) : (!hasData && !hasScreenshots ? {t('run_content.empty_output')} : null)} {hasData && ( {isLegacyData && ( renderDataTable( legacyData, legacyColumns, t('run_content.captured_data.title'), 'data.csv', 'data.json' ) )} {!isLegacyData && ( <> {renderDataTable( schemaData, schemaColumns, t('run_content.captured_data.schema_title'), 'schema_data.csv', 'schema_data.json' )} {listData.length > 0 && renderDataTable( [], [], t('run_content.captured_data.list_title'), 'list_data.csv', 'list_data.json', true )} )} )} {hasScreenshots && ( <> } aria-controls="screenshot-content" id="screenshot-header" > {t('run_content.captured_screenshot.title', 'Screenshots')} {screenshotKeys.length > 1 && ( )} {`Screenshot )} ); };