Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
61 changes: 52 additions & 9 deletions frontend/src/components/logging/detailed-logs/DetailedLogs.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
ArrowLeftOutlined,
Expand Down Expand Up @@ -61,9 +61,46 @@ const DetailedLogs = () => {
const [statusFilter, setStatusFilter] = useState(null);
const [searchText, setSearchText] = useState("");
const [searchTimeout, setSearchTimeout] = useState(null);
// Store interval ID for proper cleanup
const pollingIntervalRef = useRef(null);

const filterOptions = ["COMPLETED", "PENDING", "ERROR", "EXECUTING"];

// Check if execution should continue polling
const shouldPoll = (executionDetails) => {
if (!executionDetails) return false;

const status = executionDetails?.status?.toLowerCase();
// Only poll EXECUTING or PENDING status
if (status !== "executing" && status !== "pending") {
return false;
}

// Check if execution is stale (>1 hour from creation)
// Use executedAtWithSeconds for more accurate timestamp
const createdAt = new Date(
executionDetails?.executedAtWithSeconds || executionDetails?.executedAt
);
const now = new Date();
const oneHourInMs = 60 * 60 * 1000;
const timeDifference = now - createdAt;

if (timeDifference > oneHourInMs) {
// Stopping polling in case the execution is possibly stuck
return false;
}

return true;
};

// Clear polling interval
const clearPolling = () => {
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
};

const fetchExecutionDetails = async (id) => {
try {
const url = getUrl(`/execution/${id}/`);
Expand Down Expand Up @@ -289,21 +326,27 @@ const DetailedLogs = () => {
fetchExecutionFiles(id, pagination.current);
}, [pagination.current, ordering, statusFilter]);

// Polling logic for execution status updates
useEffect(() => {
let interval = null;
if (executionDetails?.status === "EXECUTING") {
interval = setInterval(() => {
// Clear any existing interval first
clearPolling();

if (shouldPoll(executionDetails)) {
pollingIntervalRef.current = setInterval(() => {
fetchExecutionDetails(id);
fetchExecutionFiles(id, pagination.current);
}, 5000);
}
return () => {
if (interval) {
clearInterval(interval);
}
};

// Cleanup when dependencies change
return clearPolling;
}, [executionDetails?.status, id, pagination.current]);

// Clear polling when component unmounts
useEffect(() => {
return clearPolling;
}, []);

useEffect(() => {
const initialColumns = columnsDetailedTable.reduce((acc, col) => {
acc[col.key] = true;
Expand Down
74 changes: 66 additions & 8 deletions frontend/src/components/logging/execution-logs/ExecutionLogs.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DatePicker, Flex, Tabs, Typography } from "antd";
import { useNavigate, useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import { useEffect, useState, useRef } from "react";

import { LogsTable } from "../logs-table/LogsTable";
import { DetailedLogs } from "../detailed-logs/DetailedLogs";
Expand Down Expand Up @@ -44,6 +44,8 @@ function ExecutionLogs() {
const [datePickerValue, setDatePickerValue] = useState(null);
const [ordering, setOrdering] = useState(null);
const [pollingIds, setPollingIds] = useState(new Set());
// Store timeouts in a ref for proper cleanup
const pollingTimeoutsRef = useRef({});
const currentPath = location.pathname !== `/${sessionDetails?.orgName}/logs`;
const items = [
{
Expand Down Expand Up @@ -94,6 +96,47 @@ function ExecutionLogs() {
}
};

// Check if execution should continue polling
const shouldPoll = (item) => {
// Only poll EXECUTING or PENDING status
if (
item?.status?.toLowerCase() !== "executing" &&
item?.status?.toLowerCase() !== "pending"
) {
return false;
}

// Check if execution is stale (>1 hour from last modification)
const modifiedAt = new Date(item?.modified_at);
const now = new Date();
const oneHourInMs = 60 * 60 * 1000;
const timeDifference = now - modifiedAt;

if (timeDifference > oneHourInMs) {
// Stopping polling in case the execution is possibly stuck
return false;
}

return true;
};

// Clear a single polling timeout
const clearPollingTimeout = (id) => {
if (pollingTimeoutsRef.current[id]) {
clearTimeout(pollingTimeoutsRef.current[id]);
delete pollingTimeoutsRef.current[id];
}
};

// Clear all polling timeouts and reset state
const clearAllPolling = () => {
Object.keys(pollingTimeoutsRef.current).forEach((id) => {
clearTimeout(pollingTimeoutsRef.current[id]);
});
pollingTimeoutsRef.current = {};
setPollingIds(new Set());
};

const pollExecutingRecord = async (id) => {
try {
const url = getUrl(`/execution/${id}/`);
Expand All @@ -118,40 +161,48 @@ function ExecutionLogs() {
total,
success: item?.status === "COMPLETED",
isError: item?.status === "ERROR",
status: item?.status,
workflowName: item?.workflow_name,
pipelineName: item?.pipeline_name || "Pipeline name not found",
successfulFiles: item?.successful_files,
failedFiles: item?.failed_files,
totalFiles: item?.total_files,
status: item?.status,
execution_time: item?.execution_time,
};

// If status is no longer executing, remove from polling
if (item?.status.toLowerCase() !== "executing") {
// If status should no longer be polled, remove from polling
if (!shouldPoll(item)) {
setPollingIds((prev) => {
const newSet = new Set(prev);
newSet.delete(id);
return newSet;
});
clearPollingTimeout(id);
}
}
return newData;
});

// Continue polling if still executing
if (item?.status === "EXECUTING") {
setTimeout(() => pollExecutingRecord(id), 5000); // Poll every 5 seconds
// Continue polling if should still poll
if (shouldPoll(item)) {
pollingTimeoutsRef.current[id] = setTimeout(
() => pollExecutingRecord(id),
5000
);
}
} catch (err) {
setPollingIds((prev) => {
const newSet = new Set(prev);
newSet.delete(id);
return newSet;
});
clearPollingTimeout(id);
}
};

const startPollingForExecuting = (records) => {
records.forEach((record) => {
if (record.status === "EXECUTING" && !pollingIds.has(record.key)) {
if (shouldPoll(record) && !pollingIds.has(record.key)) {
setPollingIds((prev) => {
const newSet = new Set(prev);
newSet.add(record.key);
Expand Down Expand Up @@ -240,8 +291,15 @@ function ExecutionLogs() {
);
};

// Clear all polling when component unmounts or view changes
useEffect(() => {
return clearAllPolling;
}, [id, activeTab]);

useEffect(() => {
if (!currentPath) {
// Clear any existing polling when fetching new logs
clearAllPolling();
setDataList([]);
fetchLogs(pagination.current);
}
Expand Down