mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-10 18:10:08 +00:00
Address PR review comments
- Flatten directory structure: remove barrel file, move files up to /jobs - Remove .passthrough() from zPreviewOutput, zPaginationInfo, zJobsListResponse - Align nullable/optional with OpenAPI spec (outputs_count now nullable) - Remove big comment block sections - Change extractWorkflow return type to unknown - Move zWorkflowContainer schema to jobTypes.ts - Add interface types to test helper functions - Add fetchHistory offset test for priority calculation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
/**
|
||||
* @fileoverview Jobs API Fetchers
|
||||
* @module platform/remote/comfyui/jobs/fetchers/fetchJobs
|
||||
* @module platform/remote/comfyui/jobs/fetchJobs
|
||||
*
|
||||
* Unified jobs API fetcher for history, queue, and job details.
|
||||
* All distributions use the /jobs endpoint.
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
|
||||
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { PromptId } from '@/schemas/apiSchema'
|
||||
|
||||
import type {
|
||||
@@ -16,12 +13,8 @@ import type {
|
||||
JobListItem,
|
||||
JobStatus,
|
||||
RawJobListItem
|
||||
} from '../types/jobTypes'
|
||||
import { zJobDetail, zJobsListResponse } from '../types/jobTypes'
|
||||
|
||||
// ============================================================================
|
||||
// Job List Fetchers
|
||||
// ============================================================================
|
||||
} from './jobTypes'
|
||||
import { zJobDetail, zJobsListResponse, zWorkflowContainer } from './jobTypes'
|
||||
|
||||
interface FetchJobsRawResult {
|
||||
jobs: RawJobListItem[]
|
||||
@@ -119,10 +112,6 @@ export async function fetchQueue(
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Job Detail Fetcher
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetches full job details from /jobs/{job_id}
|
||||
*/
|
||||
@@ -145,33 +134,13 @@ export async function fetchJobDetail(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for workflow container structure.
|
||||
* Full workflow validation happens downstream via validateComfyWorkflow.
|
||||
*/
|
||||
const zWorkflowContainer = z.object({
|
||||
extra_data: z
|
||||
.object({
|
||||
extra_pnginfo: z
|
||||
.object({
|
||||
workflow: z.unknown()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
|
||||
/**
|
||||
* Extracts workflow from job detail response.
|
||||
* The workflow is nested at: workflow.extra_data.extra_pnginfo.workflow
|
||||
* Full workflow validation happens downstream via validateComfyWorkflow.
|
||||
*/
|
||||
export function extractWorkflow(
|
||||
job: JobDetail | undefined
|
||||
): ComfyWorkflowJSON | undefined {
|
||||
export function extractWorkflow(job: JobDetail | undefined): unknown {
|
||||
const parsed = zWorkflowContainer.safeParse(job?.workflow)
|
||||
if (!parsed.success) return undefined
|
||||
// Full workflow validation happens downstream via validateComfyWorkflow
|
||||
return parsed.data.extra_data?.extra_pnginfo?.workflow as
|
||||
| ComfyWorkflowJSON
|
||||
| undefined
|
||||
return parsed.data.extra_data?.extra_pnginfo?.workflow
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @fileoverview Jobs API module
|
||||
* @module platform/remote/comfyui/jobs
|
||||
*
|
||||
* Unified jobs API for history, queue, and job details.
|
||||
*/
|
||||
|
||||
export {
|
||||
extractWorkflow,
|
||||
fetchHistory,
|
||||
fetchJobDetail,
|
||||
fetchQueue
|
||||
} from './fetchers/fetchJobs'
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @fileoverview Jobs API types - Backend job API format
|
||||
* @module platform/remote/comfyui/jobs/types/jobTypes
|
||||
* @module platform/remote/comfyui/jobs/jobTypes
|
||||
*
|
||||
* These types represent the jobs API format returned by the backend.
|
||||
* Jobs API provides a memory-optimized alternative to history API.
|
||||
@@ -10,10 +10,6 @@ import { z } from 'zod'
|
||||
|
||||
import { resultItemType, zTaskOutput } from '@/schemas/apiSchema'
|
||||
|
||||
// ============================================================================
|
||||
// Zod Schemas
|
||||
// ============================================================================
|
||||
|
||||
const zJobStatus = z.enum([
|
||||
'pending',
|
||||
'in_progress',
|
||||
@@ -22,13 +18,11 @@ const zJobStatus = z.enum([
|
||||
'cancelled'
|
||||
])
|
||||
|
||||
const zPreviewOutput = z
|
||||
.object({
|
||||
filename: z.string(),
|
||||
subfolder: z.string(),
|
||||
type: resultItemType
|
||||
})
|
||||
.passthrough() // Allow extra fields like nodeId, mediaType
|
||||
const zPreviewOutput = z.object({
|
||||
filename: z.string(),
|
||||
subfolder: z.string(),
|
||||
type: resultItemType
|
||||
})
|
||||
|
||||
/**
|
||||
* Execution error details for error jobs.
|
||||
@@ -60,8 +54,8 @@ const zRawJobListItem = z
|
||||
execution_start_time: z.number().nullable().optional(),
|
||||
execution_end_time: z.number().nullable().optional(),
|
||||
preview_output: zPreviewOutput.nullable().optional(),
|
||||
outputs_count: z.number().optional(),
|
||||
execution_error: zExecutionError.nullable().optional(),
|
||||
outputs_count: z.number().nullable().optional(),
|
||||
execution_error: zExecutionError.optional(),
|
||||
workflow_id: z.string().nullable().optional(),
|
||||
priority: z.number().optional()
|
||||
})
|
||||
@@ -81,31 +75,30 @@ export const zJobDetail = zRawJobListItem
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
/**
|
||||
* Pagination info from API
|
||||
*/
|
||||
const zPaginationInfo = z
|
||||
.object({
|
||||
offset: z.number(),
|
||||
limit: z.number(),
|
||||
total: z.number(),
|
||||
has_more: z.boolean()
|
||||
})
|
||||
.passthrough()
|
||||
const zPaginationInfo = z.object({
|
||||
offset: z.number(),
|
||||
limit: z.number(),
|
||||
total: z.number(),
|
||||
has_more: z.boolean()
|
||||
})
|
||||
|
||||
/**
|
||||
* Jobs list response structure
|
||||
*/
|
||||
export const zJobsListResponse = z
|
||||
.object({
|
||||
jobs: z.array(zRawJobListItem),
|
||||
pagination: zPaginationInfo
|
||||
})
|
||||
.passthrough()
|
||||
export const zJobsListResponse = z.object({
|
||||
jobs: z.array(zRawJobListItem),
|
||||
pagination: zPaginationInfo
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// TypeScript Types (derived from Zod schemas)
|
||||
// ============================================================================
|
||||
/** Schema for workflow container structure in job detail responses */
|
||||
export const zWorkflowContainer = z.object({
|
||||
extra_data: z
|
||||
.object({
|
||||
extra_pnginfo: z
|
||||
.object({
|
||||
workflow: z.unknown()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
|
||||
export type JobStatus = z.infer<typeof zJobStatus>
|
||||
export type RawJobListItem = z.infer<typeof zRawJobListItem>
|
||||
@@ -1,18 +1,24 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { z } from 'zod'
|
||||
|
||||
import {
|
||||
extractWorkflow,
|
||||
fetchHistory,
|
||||
fetchJobDetail,
|
||||
fetchQueue
|
||||
} from '@/platform/remote/comfyui/jobs'
|
||||
} from '@/platform/remote/comfyui/jobs/fetchJobs'
|
||||
import type {
|
||||
RawJobListItem,
|
||||
zJobsListResponse
|
||||
} from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
|
||||
type JobsListResponse = z.infer<typeof zJobsListResponse>
|
||||
|
||||
// Helper to create a mock job
|
||||
function createMockJob(
|
||||
id: string,
|
||||
status: 'pending' | 'in_progress' | 'completed' = 'completed',
|
||||
overrides: Record<string, unknown> = {}
|
||||
) {
|
||||
overrides: Partial<RawJobListItem> = {}
|
||||
): RawJobListItem {
|
||||
return {
|
||||
id,
|
||||
status,
|
||||
@@ -25,11 +31,10 @@ function createMockJob(
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create mock API response
|
||||
function createMockResponse(
|
||||
jobs: ReturnType<typeof createMockJob>[],
|
||||
jobs: RawJobListItem[],
|
||||
total: number = jobs.length
|
||||
) {
|
||||
): JobsListResponse {
|
||||
return {
|
||||
jobs,
|
||||
pagination: {
|
||||
@@ -89,6 +94,32 @@ describe('fetchJobs', () => {
|
||||
expect(result[2].priority).toBe(1) // total - 0 - 2
|
||||
})
|
||||
|
||||
it('calculates priority correctly with non-zero offset', async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve(
|
||||
createMockResponse(
|
||||
[
|
||||
createMockJob('job4', 'completed'),
|
||||
createMockJob('job5', 'completed')
|
||||
],
|
||||
10 // total of 10 jobs
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
// Fetch page 2 (offset=5)
|
||||
const result = await fetchHistory(mockFetch, 200, 5)
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'/jobs?status=completed&limit=200&offset=5'
|
||||
)
|
||||
// Priority base is total - offset = 10 - 5 = 5
|
||||
expect(result[0].priority).toBe(5) // (total - offset) - 0
|
||||
expect(result[1].priority).toBe(4) // (total - offset) - 1
|
||||
})
|
||||
|
||||
it('preserves server-provided priority', async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
Reference in New Issue
Block a user