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
12 changes: 1 addition & 11 deletions components/StatsDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,7 @@ import React from 'react'
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts'
import { ActionJobResult, WorkflowRunStatus } from '../src/api/generated'
import { Surface, SectionTitle } from './Surface'

/** Format a duration in seconds into a compact human string. */
function formatDuration(seconds: number): string {
if (!isFinite(seconds) || seconds <= 0) return '—'
if (seconds < 60) return `${Math.round(seconds)}s`
const mins = seconds / 60
if (mins < 60) return `${mins.toFixed(1)}m`
const hrs = Math.floor(mins / 60)
const rem = Math.round(mins % 60)
return `${hrs}h ${rem}m`
}
import { formatDuration } from '../utils/format'

interface DashboardStats {
total: number
Expand Down
72 changes: 54 additions & 18 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,20 @@ import { Surface, SectionTitle } from '../components/Surface'
import { BrandSelect } from '../components/Inputs'
import { Pager } from '../components/Pager'
import analytic from '../global/mixpanel'
import { useGetBranch, useGetGitcommit } from '../src/api/generated'
import { useGetBranch, useGetGitcommit, WorkflowRunStatus } from '../src/api/generated'
import { formatDuration } from '../utils/format'
import { StatusToColor, StatusToHumanText } from './workflow/[id]'

const DEFAULT_REPO = 'comfyanonymous/ComfyUI'

// Status filter options. The /gitcommit API has no status param, so this filters
// the rows already loaded for the current page (see note rendered below the table).
const STATUS_OPTIONS: { label: string; value: string }[] = [
{ label: 'Failed', value: WorkflowRunStatus.WorkflowRunStatusFailed },
{ label: 'In Progress', value: WorkflowRunStatus.WorkflowRunStatusStarted },
{ label: 'Success', value: WorkflowRunStatus.WorkflowRunStatusCompleted },
]

function GitCommitsList() {
const [currentPage, setCurrentPage] = React.useState(1)
const onPageChange = (page: number) => setCurrentPage(page)
Expand All @@ -26,6 +35,8 @@ function GitCommitsList() {
const [branchFilter, setBranchFilter] = React.useState<string>('Select Branch')
const [commitId, setCommitId] = React.useState<string>('')
const [workflowNameFilter, setWorkflowFilter] = React.useState<string>('')
// Client-side only (the API has no status param) — not synced to the URL.
const [statusFilter, setStatusFilter] = React.useState<string>('')

const prevFilters = React.useRef({ filterOS, repoFilter, branchFilter, commitId, workflowNameFilter, currentPage });

Expand Down Expand Up @@ -56,7 +67,6 @@ function GitCommitsList() {
workflowName: workflowNameFilter || undefined,
page: currentPage.toString(),
};
console.log(`Updating url parameters due to filter change, ${JSON.stringify(query)} vs ${JSON.stringify(prevFilters.current)}`)
router.push({ pathname: router.pathname, query }, undefined, { shallow: true });

// Update the ref with the new values
Expand Down Expand Up @@ -88,17 +98,22 @@ function GitCommitsList() {
}, [router.query.repo]);

const jobResults = filteredJobResults?.jobResults ?? []
const visibleResults = statusFilter
? jobResults.filter((r) => r.status === statusFilter)
: jobResults
const hasActiveFilters =
filterOS !== 'Select OS' ||
branchFilter !== 'Select Branch' ||
commitId !== '' ||
workflowNameFilter !== ''
workflowNameFilter !== '' ||
statusFilter !== ''

const clearAll = () => {
setFilterOS('Select OS')
setBranchFilter('Select Branch')
setCommitId('')
setWorkflowFilter('')
setStatusFilter('')
setCurrentPage(1)
}

Expand Down Expand Up @@ -170,6 +185,22 @@ function GitCommitsList() {
<option value="macos">macos</option>
<option value="windows">windows</option>
</BrandSelect>
<BrandSelect
id="status-select"
value={statusFilter}
onChange={(e) => {
setStatusFilter(e.target.value)
analytic.track('Change Status Filter', { status: e.target.value })
}}
className="w-44"
>
<option value="">Select Status</option>
{STATUS_OPTIONS.map((s) => (
<option key={s.value} value={s.value}>
{s.label}
</option>
))}
</BrandSelect>
<ClearableLabel
id="commit-id-input"
label="Commit ID"
Expand Down Expand Up @@ -206,6 +237,12 @@ function GitCommitsList() {
</Surface>
) : (
<>
{statusFilter && (
<p className="mb-2 text-xs text-ash-500 dark:text-smoke-800">
Showing {visibleResults.length} of {jobResults.length} run{jobResults.length === 1 ? '' : 's'} on this page matching{' '}
<span className="font-semibold">{StatusToHumanText(statusFilter as WorkflowRunStatus)}</span>.
</p>
)}
<Surface className="overflow-hidden">
<div className="overflow-x-auto scrollbar-thin">
<table className="w-full text-left text-sm">
Expand All @@ -221,10 +258,20 @@ function GitCommitsList() {
</tr>
</thead>
<tbody className="divide-y divide-smoke-200 dark:divide-charcoal-400/40">
{jobResults.map((result, index) => (
{visibleResults.length === 0 && (
<tr>
<td colSpan={7} className="px-5 py-10 text-center text-sm text-ash-500 dark:text-smoke-800">
No runs match the selected status on this page.
</td>
</tr>
)}
{visibleResults.map((result, index) => (
<tr
key={index}
className="group transition-colors hover:bg-smoke-200/60 dark:hover:bg-charcoal-700/40"
key={result.id || index}
className={`group transition-colors hover:bg-smoke-200/60 dark:hover:bg-charcoal-700/40 ${result.status === WorkflowRunStatus.WorkflowRunStatusFailed
? 'bg-red-500/[0.06] dark:bg-red-500/[0.08]'
: ''
}`}
>
{/* Workflow */}
<td className="px-5 py-4 align-top">
Expand Down Expand Up @@ -339,7 +386,7 @@ function GitCommitsList() {
<td className="px-5 py-4 align-top">
<span className="font-mono text-sm tabular-nums">
{result.end_time && result.start_time
? calculateTimeDifference(result.end_time, result.start_time)
? formatDuration(Math.abs(result.end_time - result.start_time))
: 'unknown'}
</span>
</td>
Expand Down Expand Up @@ -376,15 +423,4 @@ function GitCommitsList() {
);
}

export function calculateTimeDifference(startTime: number, endTime: number): string {
const differenceInSeconds = Math.abs(endTime - startTime)
const difference =
differenceInSeconds >= 60
? parseFloat((differenceInSeconds / 60).toFixed(1))
: differenceInSeconds
return differenceInSeconds >= 60
? `${difference} minute`
: `${difference} sec`
}

export default GitCommitsList
6 changes: 3 additions & 3 deletions pages/workflow/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { WorkflowStatusButton } from "../../components/StatusButton";
import { Surface, SectionTitle } from "../../components/Surface";
import UsageGraph from "../../components/UsageGraph";
import LongTextPreview from "../../components/LongTextPreview";
import { calculateTimeDifference } from "..";
import { formatDuration } from "../../utils/format";

const Row: React.FC<React.PropsWithChildren<{ label: string }>> = ({ label, children }) => (
<div className="flex flex-col gap-1 border-b border-smoke-200 dark:border-charcoal-400/40 py-3 sm:flex-row sm:items-start sm:gap-4 last:border-0">
Expand Down Expand Up @@ -96,7 +96,7 @@ function WorkflowResultDetail() {
</span>
<span className="font-mono text-sm tabular-nums text-charcoal-800 dark:text-smoke-200">
{workflowResult.end_time && workflowResult.start_time
? calculateTimeDifference(workflowResult.end_time, workflowResult.start_time)
? formatDuration(Math.abs(workflowResult.end_time - workflowResult.start_time))
: 'unknown'}
</span>
</div>
Expand Down Expand Up @@ -150,7 +150,7 @@ function WorkflowResultDetail() {
<Row label="Triggered By">{workflowResult?.job_trigger_user}</Row>
<Row label="Run Time">
{workflowResult.end_time && workflowResult.start_time
? calculateTimeDifference(workflowResult.end_time, workflowResult.start_time)
? formatDuration(Math.abs(workflowResult.end_time - workflowResult.start_time))
: "unknown"}
</Row>
<Row label="Versions">
Expand Down
10 changes: 10 additions & 0 deletions utils/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** Format a duration in seconds into a compact human string (e.g. "45s", "1.5m", "1h 3m"). */
export function formatDuration(seconds: number): string {
if (!isFinite(seconds) || seconds <= 0) return '—'
if (seconds < 60) return `${Math.round(seconds)}s`
const mins = seconds / 60
if (mins < 60) return `${mins.toFixed(1)}m`
const hrs = Math.floor(mins / 60)
const rem = Math.round(mins % 60)
return `${hrs}h ${rem}m`
Comment on lines +7 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix hour-boundary rounding (1h 60m can appear).

At Line 8, rounding remainder minutes can produce 60, which renders invalid hour/minute output (time rhyme: minutes shouldn’t hit sixty twice).

Suggested patch
 export function formatDuration(seconds: number): string {
     if (!isFinite(seconds) || seconds <= 0) return '—'
     if (seconds < 60) return `${Math.round(seconds)}s`
     const mins = seconds / 60
-    if (mins < 60) return `${mins.toFixed(1)}m`
-    const hrs = Math.floor(mins / 60)
-    const rem = Math.round(mins % 60)
+    const totalMinutes = Math.round(mins)
+    if (totalMinutes < 60) return `${mins.toFixed(1)}m`
+    const hrs = Math.floor(totalMinutes / 60)
+    const rem = totalMinutes % 60
     return `${hrs}h ${rem}m`
 }
📝 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
const hrs = Math.floor(mins / 60)
const rem = Math.round(mins % 60)
return `${hrs}h ${rem}m`
export function formatDuration(seconds: number): string {
if (!isFinite(seconds) || seconds <= 0) return '—'
if (seconds < 60) return `${Math.round(seconds)}s`
const mins = seconds / 60
const totalMinutes = Math.round(mins)
if (totalMinutes < 60) return `${mins.toFixed(1)}m`
const hrs = Math.floor(totalMinutes / 60)
const rem = totalMinutes % 60
return `${hrs}h ${rem}m`
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utils/format.ts` around lines 7 - 9, The current conversion computes hrs =
Math.floor(mins / 60) and rem = Math.round(mins % 60), but rounding can make rem
== 60 (e.g., "1h 60m"); update the logic in utils/format.ts to detect rem === 60
and if so increment hrs by 1 and set rem to 0 before returning the formatted
string (referencing the hrs and rem variables used in the diff).

}
Loading