From 2192dceda520c03b4415c9763b0ffa6f4a347602 Mon Sep 17 00:00:00 2001 From: Keshav Dayal Date: Tue, 27 Jan 2026 01:13:18 +0530 Subject: [PATCH 1/2] feat: issue 13 & issue 14 --- frontend/src/pages/Logs.js | 38 ++++++----- frontend/src/pages/Tasks.js | 132 ++++++++++++++++++++++++++++++++---- 2 files changed, 139 insertions(+), 31 deletions(-) diff --git a/frontend/src/pages/Logs.js b/frontend/src/pages/Logs.js index f42d827..e693958 100644 --- a/frontend/src/pages/Logs.js +++ b/frontend/src/pages/Logs.js @@ -296,13 +296,14 @@ const Logs = () => { const dashboardData = await taskService.getDashboardData(); console.log('Dashboard data for logs:', dashboardData); - console.log('Submitted tasks found:', dashboardData.submitted_tasks?.length || 0); + console.log('Tasks found:', dashboardData.tasks?.length || 0); - if (Array.isArray(dashboardData.submitted_tasks)) { - for (const task of dashboardData.submitted_tasks.slice(-10)) { + if (Array.isArray(dashboardData.tasks)) { + for (const task of dashboardData.tasks.slice(-20)) { // Get last 20 tasks try { - console.log('Loading log for task:', task.task_id, 'from:', task.tes_url); - const logResponse = await logService.getTaskLogs(task.task_id, task.tes_url); + console.log('Loading log for task:', task.task_id || task.id, 'from:', task.tes_url); + const taskId = task.task_id || task.id; + const logResponse = await logService.getTaskLogs(taskId, task.tes_url); let content = 'No log content available'; console.log('Task log response:', logResponse); @@ -310,34 +311,35 @@ const Logs = () => { if (logResponse && logResponse.success) { content = logResponse.log || 'Log endpoint returned success but no log content'; } else if (logResponse && logResponse.log) { - content = logResponse.log; } else { - content = `Task Log for ${task.task_id}\n\nTask Details:\n- Status: ${task.status}\n- TES Instance: ${task.tes_name}\n- Submitted: ${task.submitted_at}\n- Type: ${task.type || 'Unknown'}\n\nNote: Full log details not available (task may still be running)`; + content = `Task Log for ${taskId}\n\nTask Details:\n- Name: ${task.name || task.task_name || 'Unnamed'}\n- Status: ${task.status || task.state}\n- TES Instance: ${task.tes_name || 'Unknown'}\n- Submitted: ${task.submitted_at || task.creation_time}\n- Type: ${task.type || 'Task Submission'}\n\nNote: Full log details not available (task may still be running or logs not yet generated)`; } allLogs.push({ - id: task.task_id, + id: taskId, type: 'task', - title: `Task ${task.task_id} (${task.tes_name})`, + title: `Task ${taskId} (${task.tes_name || 'Unknown'})`, content: content, - timestamp: task.submitted_at || new Date().toISOString(), + timestamp: task.submitted_at || task.creation_time || new Date().toISOString(), metadata: { - status: task.status, + status: task.status || task.state, tesInstance: task.tes_name || 'Unknown' } }); } catch (err) { - console.error('Error loading task log for', task.task_id, ':', err); + console.error('Error loading task log for', task.task_id || task.id, ':', err); + const taskId = task.task_id || task.id; allLogs.push({ - id: task.task_id, + id: taskId, type: 'task', - title: `Task ${task.task_id} (${task.tes_name})`, - content: `Task Log for ${task.task_id}\n\nError: ${err.message}\n\nTask Details:\n- Status: ${task.status}\n- TES Instance: ${task.tes_name}\n- Submitted: ${task.submitted_at}\n- Type: ${task.type || 'Unknown'}\n\nNote: This task exists but logs couldn't be fetched.`, - timestamp: task.submitted_at || new Date().toISOString(), + title: `Task ${taskId} (${task.tes_name || 'Unknown'})`, + content: `Task Log for ${taskId}\n\nError: ${err.message}\n\nTask Details:\n- Name: ${task.name || task.task_name || 'Unnamed'}\n- Status: ${task.status || task.state}\n- TES Instance: ${task.tes_name || 'Unknown'}\n- Submitted: ${task.submitted_at || task.creation_time}\n- Type: ${task.type || 'Task Submission'}\n\nNote: This task exists but logs couldn't be fetched.`, + timestamp: task.submitted_at || task.creation_time || new Date().toISOString(), metadata: { - status: task.status, + status: task.status || task.state, tesInstance: task.tes_name || 'Unknown' - } + }, + tesInstance: task.tes_name || 'Unknown' }); } } diff --git a/frontend/src/pages/Tasks.js b/frontend/src/pages/Tasks.js index f921d76..389c6dd 100644 --- a/frontend/src/pages/Tasks.js +++ b/frontend/src/pages/Tasks.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { taskService } from '../services/taskService'; @@ -13,7 +13,9 @@ import { Plus, Eye, FileText, - Search + Search, + ChevronUp, + ChevronDown } from 'lucide-react'; const PageContainer = styled.div` @@ -133,6 +135,21 @@ const TableHeader = styled.th` font-weight: 600; color: #495057; font-size: 14px; + cursor: ${props => props.sortable ? 'pointer' : 'default'}; + user-select: none; + position: relative; + + &:hover { + background: ${props => props.sortable ? '#e9ecef' : '#f8f9fa'}; + } +`; + +const SortIndicator = styled.span` + display: inline-flex; + align-items: center; + margin-left: 4px; + opacity: ${props => props.active ? '1' : '0.3'}; + transition: opacity 0.2s; `; const TableRow = styled.tr` @@ -193,6 +210,8 @@ const Tasks = () => { const navigate = useNavigate(); const [searchTerm, setSearchTerm] = useState(''); const [filteredTasks, setFilteredTasks] = useState([]); + const [sortColumn, setSortColumn] = useState('creation_time'); + const [sortDirection, setSortDirection] = useState('desc'); // 'asc' or 'desc' const { data: tasksData, @@ -201,6 +220,75 @@ const Tasks = () => { refetch } = usePolling(taskService.listTasks, POLLING_INTERVALS.NORMAL); + const handleSort = (column) => { + if (sortColumn === column) { + // Toggle direction if clicking same column + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); + } else { + // Set new column with ascending as default + setSortColumn(column); + setSortDirection('asc'); + } + }; + + const getSortIndicator = (column) => { + if (sortColumn !== column) { + return ( + + + + ); + } + return ( + + {sortDirection === 'asc' ? : } + + ); + }; + + const sortTasks = useCallback((tasks) => { + const sorted = [...tasks].sort((a, b) => { + let aValue, bValue; + + switch (sortColumn) { + case 'id': + aValue = a.id || ''; + bValue = b.id || ''; + break; + case 'name': + aValue = (a.name || '').toLowerCase(); + bValue = (b.name || '').toLowerCase(); + break; + case 'state': + aValue = a.state || ''; + bValue = b.state || ''; + break; + case 'tes_name': + aValue = (a.tes_name || a.tes_url || '').toLowerCase(); + bValue = (b.tes_name || b.tes_url || '').toLowerCase(); + break; + case 'creation_time': + // Parse dates for comparison + aValue = a.creation_time ? new Date(a.creation_time).getTime() : 0; + bValue = b.creation_time ? new Date(b.creation_time).getTime() : 0; + break; + default: + return 0; + } + + // Handle comparison + if (aValue < bValue) { + return sortDirection === 'asc' ? -1 : 1; + } + if (aValue > bValue) { + return sortDirection === 'asc' ? 1 : -1; + } + return 0; + }); + + return sorted; + }, [sortColumn, sortDirection]); + const handleCancelTask = async (tesUrl, taskId) => { if (window.confirm('Are you sure you want to cancel this task?')) { try { @@ -238,19 +326,22 @@ const Tasks = () => { !task.error_prone_instance; }); - if (!searchTerm) { - setFilteredTasks(healthyTasks); - } else { - const filtered = healthyTasks.filter(task => + // Apply search filter + let tasksToDisplay = healthyTasks; + if (searchTerm) { + tasksToDisplay = healthyTasks.filter(task => task.id?.toLowerCase().includes(searchTerm.toLowerCase()) || task.name?.toLowerCase().includes(searchTerm.toLowerCase()) || task.state?.toLowerCase().includes(searchTerm.toLowerCase()) || task.tes_url?.toLowerCase().includes(searchTerm.toLowerCase()) || task.tes_name?.toLowerCase().includes(searchTerm.toLowerCase()) ); - setFilteredTasks(filtered); } - }, [tasksData, searchTerm]); + + // Apply sorting + const sortedTasks = sortTasks(tasksToDisplay); + setFilteredTasks(sortedTasks); + }, [tasksData, searchTerm, sortColumn, sortDirection, sortTasks]); return ( @@ -306,11 +397,26 @@ const Tasks = () => { - Task ID - Name - Status - TES Instance - Created + handleSort('id')}> + Task ID + {getSortIndicator('id')} + + handleSort('name')}> + Name + {getSortIndicator('name')} + + handleSort('state')}> + Status + {getSortIndicator('state')} + + handleSort('tes_name')}> + TES Instance + {getSortIndicator('tes_name')} + + handleSort('creation_time')}> + Created + {getSortIndicator('creation_time')} + Actions From 94b0ff24c19ea8f7e1156d0826e508603d816fe9 Mon Sep 17 00:00:00 2001 From: Keshav Dayal Date: Tue, 27 Jan 2026 01:28:17 +0530 Subject: [PATCH 2/2] feat: suggested changes --- frontend/src/pages/Logs.js | 4 ++-- frontend/src/pages/Tasks.js | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/Logs.js b/frontend/src/pages/Logs.js index e693958..bdc91ba 100644 --- a/frontend/src/pages/Logs.js +++ b/frontend/src/pages/Logs.js @@ -311,6 +311,7 @@ const Logs = () => { if (logResponse && logResponse.success) { content = logResponse.log || 'Log endpoint returned success but no log content'; } else if (logResponse && logResponse.log) { + content = logResponse.log; } else { content = `Task Log for ${taskId}\n\nTask Details:\n- Name: ${task.name || task.task_name || 'Unnamed'}\n- Status: ${task.status || task.state}\n- TES Instance: ${task.tes_name || 'Unknown'}\n- Submitted: ${task.submitted_at || task.creation_time}\n- Type: ${task.type || 'Task Submission'}\n\nNote: Full log details not available (task may still be running or logs not yet generated)`; } @@ -338,8 +339,7 @@ const Logs = () => { metadata: { status: task.status || task.state, tesInstance: task.tes_name || 'Unknown' - }, - tesInstance: task.tes_name || 'Unknown' + } }); } } diff --git a/frontend/src/pages/Tasks.js b/frontend/src/pages/Tasks.js index 389c6dd..779290a 100644 --- a/frontend/src/pages/Tasks.js +++ b/frontend/src/pages/Tasks.js @@ -222,8 +222,7 @@ const Tasks = () => { const handleSort = (column) => { if (sortColumn === column) { - // Toggle direction if clicking same column - setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); + setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc'); } else { // Set new column with ascending as default setSortColumn(column); @@ -341,7 +340,7 @@ const Tasks = () => { // Apply sorting const sortedTasks = sortTasks(tasksToDisplay); setFilteredTasks(sortedTasks); - }, [tasksData, searchTerm, sortColumn, sortDirection, sortTasks]); + }, [tasksData, searchTerm, sortTasks]); return (