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
47 changes: 39 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

124 changes: 106 additions & 18 deletions src/components/Collections/Optimizations/Optimizations.jsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,126 @@
import React, { memo, useState, useEffect, useCallback } from 'react';
import React, { memo, useState, useEffect, useCallback, useRef } from 'react';
import PropTypes from 'prop-types';
import { axiosInstance as axios } from '../../../common/axios';
import { Box } from '@mui/material';
import ProgressGrid from './ProgressGrid/ProgressGrid';
import Timeline from './Timeline/Timeline';
import OptimizationsTree from './Tree/OptimizationsTree';

/** Poll interval while at least one optimization is running and the tab is visible (2–5s range). */
const POLL_ACTIVE_MS = 4000;
Comment thread
trean marked this conversation as resolved.
/** Max delay between retries after a failed poll (exponential backoff cap). */
const POLL_ERROR_RETRY_MAX_MS = 32000;

function isRequestCanceled(error) {
return error?.code === 'ERR_CANCELED' || error?.name === 'CanceledError';
}

const Optimizations = ({ collectionName }) => {
const [data, setData] = useState(null);
const [selectedOptimization, setSelectedOptimization] = useState(null);
const [requestTime, setRequestTime] = useState(null);
const [isRefreshing, setIsRefreshing] = useState(false);

const fetchData = useCallback(() => {
setIsRefreshing(true);
// Clear selected optimization when refreshing to show updated data
setSelectedOptimization(null);
axios
.get(`/collections/${encodeURIComponent(collectionName)}/optimizations?with=queued,completed,idle_segments`)
.then((response) => {
setData(response.data);
const abortRef = useRef(null);
const pollTimeoutRef = useRef(null);
const lastRunningRef = useRef(false);
const mountedRef = useRef(true);
const pollErrorBackoffMsRef = useRef(POLL_ACTIVE_MS);

const runFetch = useCallback(
async ({ preserveSelection = false } = {}) => {
clearTimeout(pollTimeoutRef.current);
pollTimeoutRef.current = null;

abortRef.current?.abort();
const ac = new AbortController();
abortRef.current = ac;
Comment thread
trean marked this conversation as resolved.

if (!preserveSelection) {
pollErrorBackoffMsRef.current = POLL_ACTIVE_MS;
setIsRefreshing(true);
setSelectedOptimization(null);
}

const url = `/collections/${encodeURIComponent(
collectionName
)}/optimizations?with=queued,completed,idle_segments`;

try {
const { data: next } = await axios.get(url, { signal: ac.signal });
if (!mountedRef.current) return;

setData(next);
setRequestTime(Date.now());
})
.catch((error) => {

const result = next?.result;
const hasRunning = Array.isArray(result?.running) && result.running.length > 0;
lastRunningRef.current = hasRunning;
pollErrorBackoffMsRef.current = POLL_ACTIVE_MS;

clearTimeout(pollTimeoutRef.current);
if (hasRunning && !document.hidden) {
pollTimeoutRef.current = window.setTimeout(() => {
pollTimeoutRef.current = null;
void runFetch({ preserveSelection: true });
}, POLL_ACTIVE_MS);
}
} catch (error) {
if (isRequestCanceled(error)) return;
console.error('Error fetching optimizations:', error);
})
.finally(() => {
setIsRefreshing(false);
});
}, [collectionName]);
if (mountedRef.current && lastRunningRef.current && !document.hidden) {
const delay = pollErrorBackoffMsRef.current;
pollErrorBackoffMsRef.current = Math.min(pollErrorBackoffMsRef.current * 2, POLL_ERROR_RETRY_MAX_MS);
pollTimeoutRef.current = window.setTimeout(() => {
pollTimeoutRef.current = null;
void runFetch({ preserveSelection: true });
}, delay);
}
} finally {
Comment thread
trean marked this conversation as resolved.
if (!preserveSelection && mountedRef.current) {
setIsRefreshing(false);
}
}
},
[collectionName]
);

const fetchData = useCallback(() => {
void runFetch({ preserveSelection: false });
}, [runFetch]);

useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
clearTimeout(pollTimeoutRef.current);
pollTimeoutRef.current = null;
abortRef.current?.abort();
};
}, []);

useEffect(() => {
// this is used to stop polling when the user switches to another tab
const onVisibilityChange = () => {
if (document.hidden) {
clearTimeout(pollTimeoutRef.current);
pollTimeoutRef.current = null;
} else if (lastRunningRef.current) {
void runFetch({ preserveSelection: true });
}
};
document.addEventListener('visibilitychange', onVisibilityChange);
return () => document.removeEventListener('visibilitychange', onVisibilityChange);
}, [runFetch]);

useEffect(() => {
fetchData();
}, [fetchData]);
void runFetch({ preserveSelection: false });
return () => {
clearTimeout(pollTimeoutRef.current);
pollTimeoutRef.current = null;
abortRef.current?.abort();
};
}, [collectionName, runFetch]);
Comment thread
trean marked this conversation as resolved.

const handleOptimizationSelect = (optimization) => {
if (optimization) {
Expand Down
Loading