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"; import { useThemeMode } from "../../context/theme-provider"; 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 [isSchemaTabular, setIsSchemaTabular] = useState(false); 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); const { darkMode } = useThemeMode(); useEffect(() => { setTab(tab); }, [interpretationInProgress]); useEffect(() => { if (row.status === 'running' || row.status === 'queued' || row.status === 'scheduled') { setSchemaData([]); setSchemaColumns([]); setListData([]); setListColumns([]); setLegacyData([]); setLegacyColumns([]); setIsLegacyData(false); setIsSchemaTabular(false); return; } 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) { processSchemaData(row.serializableOutput.scrapeSchema); } if (row.serializableOutput.scrapeList) { processScrapeList(row.serializableOutput.scrapeList); } }, [row.serializableOutput, row.status]); useEffect(() => { if (row.status === 'running' || row.status === 'queued' || row.status === 'scheduled') { setScreenshotKeys([]); setCurrentScreenshotIndex(0); return; } if (row.binaryOutput && Object.keys(row.binaryOutput).length > 0) { setScreenshotKeys(Object.keys(row.binaryOutput)); setCurrentScreenshotIndex(0); } else { setScreenshotKeys([]); setCurrentScreenshotIndex(0); } }, [row.binaryOutput, row.status]); 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 processSchemaData = (schemaOutput: any) => { if (Array.isArray(schemaOutput)) { const filteredData = schemaOutput.filter(row => row && Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { const allColumns = new Set(); filteredData.forEach(item => { Object.keys(item).forEach(key => allColumns.add(key)); }); setSchemaData(filteredData); setSchemaColumns(Array.from(allColumns)); setIsSchemaTabular(filteredData.length > 1); return; } } if (schemaOutput['schema-tabular']) { const tabularData = schemaOutput['schema-tabular']; if (Array.isArray(tabularData) && tabularData.length > 0) { const filteredData = tabularData.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); if (filteredData.length > 0) { const allColumns = new Set(); filteredData.forEach(item => { Object.keys(item).forEach(key => allColumns.add(key)); }); setSchemaData(filteredData); setSchemaColumns(Array.from(allColumns)); setIsSchemaTabular(true); return; } } } let allData: any[] = []; let hasMultipleEntries = false; Object.keys(schemaOutput).forEach(key => { const data = schemaOutput[key]; if (Array.isArray(data)) { const filteredData = data.filter(row => Object.values(row).some(value => value !== undefined && value !== "") ); allData = [...allData, ...filteredData]; if (filteredData.length > 1) hasMultipleEntries = true; } }); if (allData.length > 0) { const allColumns = new Set(); allData.forEach(item => { Object.keys(item).forEach(key => allColumns.add(key)); }); setSchemaData(allData); setSchemaColumns(Array.from(allColumns)); setIsSchemaTabular(hasMultipleEntries || allData.length > 1); } }; 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); }; const convertToCSV = (data: any[], columns: string[], isSchemaData: boolean = false, isTabular: boolean = false): string => { if (isSchemaData && !isTabular && data.length === 1) { const header = 'Label,Value'; const rows = columns.map(column => `"${column}","${data[0][column] || ""}"` ); return [header, ...rows].join('\n'); } else { const header = columns.map(col => `"${col}"`).join(','); const rows = data.map(row => columns.map(col => { const value = row[col] || ""; const escapedValue = String(value).replace(/"/g, '""'); return `"${escapedValue}"`; }).join(',') ); return [header, ...rows].join('\n'); } }; // Function to download a specific dataset as CSV const downloadCSV = (data: any[], columns: string[], filename: string, isSchemaData: boolean = false, isTabular: boolean = false) => { const csvContent = convertToCSV(data, columns, isSchemaData, isTabular); 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); setTimeout(() => { URL.revokeObjectURL(url); }, 100); }; 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, isSchemaData: 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; const shouldShowAsKeyValue = isSchemaData && !isSchemaTabular && currentData.length === 1; return ( } aria-controls={`${title.toLowerCase()}-content`} id={`${title.toLowerCase()}-header`} > {title} {isPaginatedList && listData.length > 1 && ( )} {shouldShowAsKeyValue ? ( <> Label Value ) : ( (isPaginatedList ? currentColumns : columns).map((column) => ( {column} )) )} {shouldShowAsKeyValue ? ( // Single schema entry - show as key-value pairs currentColumns.map((column) => ( {column} {currentData[0][column] === undefined || currentData[0][column] === "" ? "-" : currentData[0][column]} )) ) : ( // Multiple entries or list data - show as table currentData.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' || row.status === 'queued' ? : 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', 'Captured Texts'), 'schema_data.csv', 'schema_data.json', false, true )} {listData.length > 0 && renderDataTable( [], [], t('run_content.captured_data.list_title', 'Captured Lists'), '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 )} ); };