Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions frontend/src/pages/Logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -312,30 +313,31 @@ const Logs = () => {
} else if (logResponse && logResponse.log) {
Comment thread
sourcery-ai[bot] marked this conversation as resolved.

Copilot AI Jan 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The else if (logResponse && logResponse.log) branch is now empty, so when getTaskLogs returns a response where success is false but log is present, the log content is silently discarded and the fallback summary is used instead. This regresses the previous behavior (where content was set from logResponse.log in this case) and can hide useful log details; consider assigning content from logResponse.log in this branch.

Suggested change
} else if (logResponse && logResponse.log) {
} else if (logResponse && logResponse.log) {
content = logResponse.log;

Copilot uses AI. Check for mistakes.
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'
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
}
});
} 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'
}
});
Expand Down
131 changes: 118 additions & 13 deletions frontend/src/pages/Tasks.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -13,7 +13,9 @@ import {
Plus,
Eye,
FileText,
Search
Search,
ChevronUp,
ChevronDown
} from 'lucide-react';

const PageContainer = styled.div`
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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,
Expand All @@ -201,6 +220,74 @@ const Tasks = () => {
refetch
} = usePolling(taskService.listTasks, POLLING_INTERVALS.NORMAL);

const handleSort = (column) => {
if (sortColumn === column) {
setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc');
} else {
// Set new column with ascending as default
setSortColumn(column);
setSortDirection('asc');
}
};

const getSortIndicator = (column) => {
if (sortColumn !== column) {
return (
<SortIndicator active={false}>
<ChevronUp size={14} />
</SortIndicator>
);
}
return (
<SortIndicator active={true}>
{sortDirection === 'asc' ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
</SortIndicator>
);
};

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 {
Expand Down Expand Up @@ -238,19 +325,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, sortTasks]);

return (
<PageContainer>
Expand Down Expand Up @@ -306,11 +396,26 @@ const Tasks = () => {
<Table>
<thead>
<tr>
<TableHeader>Task ID</TableHeader>
<TableHeader>Name</TableHeader>
<TableHeader>Status</TableHeader>
<TableHeader>TES Instance</TableHeader>
<TableHeader>Created</TableHeader>
<TableHeader sortable onClick={() => handleSort('id')}>
Task ID
{getSortIndicator('id')}
</TableHeader>
<TableHeader sortable onClick={() => handleSort('name')}>
Name
{getSortIndicator('name')}
</TableHeader>
<TableHeader sortable onClick={() => handleSort('state')}>
Status
{getSortIndicator('state')}
</TableHeader>
<TableHeader sortable onClick={() => handleSort('tes_name')}>
TES Instance
{getSortIndicator('tes_name')}
</TableHeader>
<TableHeader sortable onClick={() => handleSort('creation_time')}>
Created
{getSortIndicator('creation_time')}
</TableHeader>
<TableHeader>Actions</TableHeader>
</tr>
</thead>
Expand Down
Loading