Repository navigation
feat: add auto-refresh frequency dropdown to dashboard #394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||
|
|
@@ -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); | ||||||||||||||||||||||||||||||||||||
|
|
@@ -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]); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const handlePageChange = (newPage: number) => { | ||||||||||||||||||||||||||||||||||||
| setCurrentPage(newPage); | ||||||||||||||||||||||||||||||||||||
| setSelectedRunId(null); | ||||||||||||||||||||||||||||||||||||
|
|
@@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
🧰 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 (lint/a11y/useButtonType) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| {/* Graph Visualization */} | ||||||||||||||||||||||||||||||||||||
| {showGraph && selectedRunId && ( | ||||||||||||||||||||||||||||||||||||
| <div className="mb-6"> | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
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.
🤖 Prompt for AI Agents