Skip to content
Merged
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
74 changes: 67 additions & 7 deletions dashboard/src/components/RunsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ interface RunsTableProps {
namespace: string;
}

const REFRESH_OPTIONS = [
{ label: "Off", value: 0 },
{ label: "5 seconds", value: 5000 },
{ label: "10 seconds", value: 10000 },
{ label: "30 seconds", value: 30000 },
{ label: "1 minute", value: 60000 },
] as const;

type RefreshMs = typeof REFRESH_OPTIONS[number]['value'];

export const RunsTable: React.FC<RunsTableProps> = ({
namespace
}) => {
Expand All @@ -33,6 +43,7 @@ export const RunsTable: React.FC<RunsTableProps> = ({
const [error, setError] = useState<string | null>(null);
const [selectedRunId, setSelectedRunId] = useState<string | null>(null);
const [showGraph, setShowGraph] = useState(false);
const [refreshInterval, setRefreshInterval] = useState<RefreshMs>(0);

const loadRuns = useCallback(async (page: number, size: number) => {
setIsLoading(true);
Expand All @@ -54,6 +65,30 @@ export const RunsTable: React.FC<RunsTableProps> = ({
}
}, [namespace, currentPage, pageSize, loadRuns]);

useEffect(() => {
if (refreshInterval === 0) {
return;
}

let isCancelled = false;
let timeoutId: ReturnType<typeof setTimeout>;

const poll = () => {
loadRuns(currentPage, pageSize).finally(() => {
if (!isCancelled) {
timeoutId = setTimeout(poll, refreshInterval);
}
});
};

timeoutId = setTimeout(poll, refreshInterval);

return () => {
isCancelled = true;
clearTimeout(timeoutId);
};
}, [refreshInterval, currentPage, pageSize, loadRuns]);
Comment on lines +68 to +90

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Trigger an immediate refresh and pause work when tab is hidden; keep non-overlapping loop.

Current recursive timeout is good (no overlaps), but it waits one interval before first fetch and does work while the tab is hidden.

 useEffect(() => {
   if (refreshInterval === 0) {
     return;
   }

   let isCancelled = false;
   let timeoutId: ReturnType<typeof setTimeout>;

   const poll = () => {
-    loadRuns(currentPage, pageSize).finally(() => {
-      if (!isCancelled) {
-        timeoutId = setTimeout(poll, refreshInterval);
-      }
-    });
+    if (document.hidden) {
+      timeoutId = setTimeout(poll, refreshInterval);
+      return;
+    }
+    loadRuns(currentPage, pageSize).finally(() => {
+      if (!isCancelled) {
+        timeoutId = setTimeout(poll, refreshInterval);
+      }
+    });
   };

-  timeoutId = setTimeout(poll, refreshInterval);
+  // Immediate refresh on enable
+  poll();

   return () => {
     isCancelled = true;
     clearTimeout(timeoutId);
   };
 }, [refreshInterval, currentPage, pageSize, loadRuns]);

Optional but recommended: add a last-wins gate to ignore stale responses from manual clicks vs. pollers.

// imports: useRef
const reqIdRef = useRef(0);

const loadRuns = useCallback(async (page: number, size: number) => {
  const id = ++reqIdRef.current;
  setIsLoading(true);
  setError(null);
  try {
    const data = await clientApiService.getRuns(namespace, page, size);
    if (id === reqIdRef.current) setRunsData(data);
  } catch (err) {
    if (id === reqIdRef.current) {
      setError(err instanceof Error ? err.message : 'Failed to load runs');
    }
  } finally {
    if (id === reqIdRef.current) setIsLoading(false);
  }
}, [namespace]);
🤖 Prompt for AI Agents
In dashboard/src/components/RunsTable.tsx around lines 66 to 88, change the
polling effect so it triggers an immediate fetch on mount, pauses scheduling
when document.visibilityState === 'hidden', and preserves the non-overlapping
recursive timeout loop; implement a request-id last-wins gate using a useRef
counter that increments per loadRuns call and only applies results/error/loading
state when the id matches the latest ref. Specifically: import and use useRef,
increment reqIdRef before each fetch, set isLoading/error state only when ids
match, call loadRuns immediately (not after first timeout), keep the
setTimeout-based poll scheduling inside finally only when the tab is visible and
not cancelled, and clear/stop timeouts and set cancel flag in the cleanup.
Ensure currentPage/pageSize/loadRuns remain in deps.


const handlePageChange = (newPage: number) => {
setCurrentPage(newPage);
setSelectedRunId(null);
Expand Down Expand Up @@ -144,15 +179,40 @@ export const RunsTable: React.FC<RunsTableProps> = ({
<p className="text-sm text-gray-600">Monitor and visualize workflow executions</p>
</div>
</div>
<button
onClick={() => loadRuns(currentPage, pageSize)}
className="flex items-center space-x-2 px-4 py-2 bg-[#031035] text-white rounded-lg hover:bg-[#0a1a4a] transition-colors"
>
<RefreshCw className="w-4 h-4" />
<span>Refresh</span>
</button>

<div className="flex items-center space-x-2">
<label
htmlFor="auto-refresh-select"
className="text-xs font-medium text-gray-600"
>
Auto-refresh
</label>
<select
id="auto-refresh-select"
value={refreshInterval}
onChange={(e) => setRefreshInterval(Number(e.target.value) as RefreshMs)}
className="px-3 py-2 text-sm border border-gray-300 rounded-lg bg-white shadow-sm
focus:outline-none focus:ring-2 focus:ring-[#031035] focus:border-[#031035]
hover:border-gray-400 transition"
>
{REFRESH_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>

<button
onClick={() => loadRuns(currentPage, pageSize)}
className="flex items-center space-x-2 px-4 py-2 bg-[#031035] text-white rounded-lg hover:bg-[#0a1a4a] transition-colors shadow-sm"
>
<RefreshCw className="w-4 h-4" />
<span>Refresh</span>
</button>
</div>
Comment on lines +205 to +212

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add explicit button type to avoid unintended form submission (Biome error)

-  <button
+  <button
+    type="button"
     onClick={() => loadRuns(currentPage, pageSize)}
     className="flex items-center space-x-2 px-4 py-2 bg-[#031035] text-white rounded-lg hover:bg-[#0a1a4a] transition-colors shadow-sm"
   >

Apply the same type="button" to other non-submit buttons in this file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
onClick={() => loadRuns(currentPage, pageSize)}
className="flex items-center space-x-2 px-4 py-2 bg-[#031035] text-white rounded-lg hover:bg-[#0a1a4a] transition-colors shadow-sm"
>
<RefreshCw className="w-4 h-4" />
<span>Refresh</span>
</button>
</div>
<button
type="button"
onClick={() => loadRuns(currentPage, pageSize)}
className="flex items-center space-x-2 px-4 py-2 bg-[#031035] text-white rounded-lg hover:bg-[#0a1a4a] transition-colors shadow-sm"
>
<RefreshCw className="w-4 h-4" />
<span>Refresh</span>
</button>
</div>
🧰 Tools
🪛 Biome (2.1.2)

[error] 205-208: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🤖 Prompt for AI Agents
In dashboard/src/components/RunsTable.tsx around lines 205 to 212, the Refresh
button (and other non-submit buttons in this file) lack an explicit type, which
can cause unintended form submissions; update the Refresh button to include
type="button" and scan the file to add type="button" to every other button
element that is not intended to submit a form so they won't trigger form submit
behavior.

</div>


{/* Graph Visualization */}
{showGraph && selectedRunId && (
<div className="mb-6">
Expand Down