mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-04 05:02:17 +00:00
[feat] Migrate to Jobs API (PR 2 of 3)
This PR switches the frontend from legacy /history and /queue endpoints to the unified /jobs API. Key changes: - Rewrite TaskItemImpl in queueStore.ts to wrap JobListItem - Update api.ts getQueue()/getHistory() to use Jobs API - Update all queue composables (useJobList, useJobMenu, useResultGallery) - Update useJobErrorReporting to use execution_error.exception_message - Update JobGroupsList.vue workflowId access - Update reconciliation.ts to work with JobListItem - Update all related tests Breaking changes: - getQueue() now returns JobListItem[] instead of legacy tuple format - getHistory() now returns JobListItem[] instead of HistoryTaskItem[] - TaskItemImpl.outputs now lazily loads via loadFullOutputs() Part of Jobs API migration. Depends on PR 1 (jobs-api-pr1-infrastructure). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useAssetsStore } from '@/stores/assetsStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import type {
|
||||
HistoryTaskItem,
|
||||
TaskPrompt,
|
||||
TaskStatus,
|
||||
TaskOutput
|
||||
} from '@/schemas/apiSchema'
|
||||
import type { JobListItem } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
|
||||
// Mock the api module
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
@@ -50,24 +45,19 @@ vi.mock('@/stores/queueStore', () => ({
|
||||
url: string
|
||||
}
|
||||
| undefined
|
||||
public promptId: string
|
||||
|
||||
constructor(
|
||||
public taskType: string,
|
||||
public prompt: TaskPrompt,
|
||||
public status: TaskStatus | undefined,
|
||||
public outputs: TaskOutput
|
||||
) {
|
||||
this.flatOutputs = this.outputs
|
||||
? [
|
||||
{
|
||||
supportsPreview: true,
|
||||
filename: 'test.png',
|
||||
subfolder: '',
|
||||
type: 'output',
|
||||
url: 'http://test.com/test.png'
|
||||
}
|
||||
]
|
||||
: []
|
||||
constructor(public job: JobListItem) {
|
||||
this.promptId = job.id
|
||||
this.flatOutputs = [
|
||||
{
|
||||
supportsPreview: true,
|
||||
filename: 'test.png',
|
||||
subfolder: '',
|
||||
type: 'output',
|
||||
url: 'http://test.com/test.png'
|
||||
}
|
||||
]
|
||||
this.previewOutput = this.flatOutputs[0]
|
||||
}
|
||||
}
|
||||
@@ -79,17 +69,17 @@ vi.mock('@/platform/assets/composables/media/assetMappers', () => ({
|
||||
id: `${type}-${index}`,
|
||||
name,
|
||||
size: 0,
|
||||
created_at: new Date(Date.now() - index * 1000).toISOString(), // Unique timestamps
|
||||
created_at: new Date(Date.now() - index * 1000).toISOString(),
|
||||
tags: [type],
|
||||
preview_url: `http://test.com/${name}`
|
||||
})),
|
||||
mapTaskOutputToAssetItem: vi.fn((task, output) => {
|
||||
const index = parseInt(task.prompt[1].split('_')[1]) || 0
|
||||
const index = parseInt(task.promptId.split('_')[1]) || 0
|
||||
return {
|
||||
id: task.prompt[1], // Use promptId as asset ID
|
||||
id: task.promptId,
|
||||
name: output.filename,
|
||||
size: 0,
|
||||
created_at: new Date(Date.now() - index * 1000).toISOString(), // Unique timestamps
|
||||
created_at: new Date(Date.now() - index * 1000).toISOString(),
|
||||
tags: ['output'],
|
||||
preview_url: output.url,
|
||||
user_metadata: {}
|
||||
@@ -100,43 +90,18 @@ vi.mock('@/platform/assets/composables/media/assetMappers', () => ({
|
||||
describe('assetsStore - Refactored (Option A)', () => {
|
||||
let store: ReturnType<typeof useAssetsStore>
|
||||
|
||||
// Helper function to create mock history items
|
||||
const createMockHistoryItem = (index: number): HistoryTaskItem => ({
|
||||
taskType: 'History' as const,
|
||||
prompt: [
|
||||
1000 + index, // queueIndex
|
||||
`prompt_${index}`, // promptId
|
||||
{}, // promptInputs
|
||||
{
|
||||
extra_pnginfo: {
|
||||
workflow: {
|
||||
last_node_id: 1,
|
||||
last_link_id: 1,
|
||||
nodes: [],
|
||||
links: [],
|
||||
groups: [],
|
||||
config: {},
|
||||
version: 1
|
||||
}
|
||||
}
|
||||
}, // extraData
|
||||
[] // outputsToExecute
|
||||
],
|
||||
status: {
|
||||
status_str: 'success' as const,
|
||||
completed: true,
|
||||
messages: []
|
||||
},
|
||||
outputs: {
|
||||
'1': {
|
||||
images: [
|
||||
{
|
||||
filename: `output_${index}.png`,
|
||||
subfolder: '',
|
||||
type: 'output' as const
|
||||
}
|
||||
]
|
||||
}
|
||||
// Helper function to create mock job items
|
||||
const createMockJobItem = (index: number): JobListItem => ({
|
||||
id: `prompt_${index}`,
|
||||
status: 'completed',
|
||||
create_time: 1000 + index,
|
||||
update_time: 1000 + index,
|
||||
last_state_update: 1000 + index,
|
||||
priority: 1000 + index,
|
||||
preview_output: {
|
||||
filename: `output_${index}.png`,
|
||||
subfolder: '',
|
||||
type: 'output'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -149,11 +114,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
describe('Initial Load', () => {
|
||||
it('should load initial history items', async () => {
|
||||
const mockHistory = Array.from({ length: 10 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValue({
|
||||
History: mockHistory
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValue(mockHistory)
|
||||
|
||||
await store.updateHistory()
|
||||
|
||||
@@ -166,11 +129,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
|
||||
it('should set hasMoreHistory to true when batch is full', async () => {
|
||||
const mockHistory = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValue({
|
||||
History: mockHistory
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValue(mockHistory)
|
||||
|
||||
await store.updateHistory()
|
||||
|
||||
@@ -194,11 +155,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
it('should accumulate items when loading more', async () => {
|
||||
// First batch - full BATCH_SIZE
|
||||
const firstBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: firstBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(firstBatch)
|
||||
|
||||
await store.updateHistory()
|
||||
expect(store.historyAssets).toHaveLength(200)
|
||||
@@ -206,11 +165,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
|
||||
// Second batch - different items
|
||||
const secondBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(200 + i)
|
||||
createMockJobItem(200 + i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: secondBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(secondBatch)
|
||||
|
||||
await store.loadMoreHistory()
|
||||
|
||||
@@ -222,24 +179,20 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
it('should prevent duplicate items during pagination', async () => {
|
||||
// First batch - full BATCH_SIZE
|
||||
const firstBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: firstBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(firstBatch)
|
||||
|
||||
await store.updateHistory()
|
||||
expect(store.historyAssets).toHaveLength(200)
|
||||
|
||||
// Second batch with some duplicates
|
||||
const secondBatch = [
|
||||
createMockHistoryItem(2), // Duplicate
|
||||
createMockHistoryItem(5), // Duplicate
|
||||
...Array.from({ length: 198 }, (_, i) => createMockHistoryItem(200 + i)) // New
|
||||
createMockJobItem(2), // Duplicate
|
||||
createMockJobItem(5), // Duplicate
|
||||
...Array.from({ length: 198 }, (_, i) => createMockJobItem(200 + i)) // New
|
||||
]
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: secondBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(secondBatch)
|
||||
|
||||
await store.loadMoreHistory()
|
||||
|
||||
@@ -255,11 +208,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
it('should stop loading when no more items', async () => {
|
||||
// First batch - less than BATCH_SIZE
|
||||
const firstBatch = Array.from({ length: 50 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: firstBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(firstBatch)
|
||||
|
||||
await store.updateHistory()
|
||||
expect(store.hasMoreHistory).toBe(false)
|
||||
@@ -274,11 +225,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
it('should handle race conditions with concurrent loads', async () => {
|
||||
// Setup initial state with full batch
|
||||
const initialBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: initialBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(initialBatch)
|
||||
await store.updateHistory()
|
||||
expect(store.hasMoreHistory).toBe(true)
|
||||
|
||||
@@ -286,12 +235,10 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
vi.mocked(api.getHistory).mockClear()
|
||||
|
||||
// Setup slow API response
|
||||
let resolveLoadMore: (value: { History: HistoryTaskItem[] }) => void
|
||||
const loadMorePromise = new Promise<{ History: HistoryTaskItem[] }>(
|
||||
(resolve) => {
|
||||
resolveLoadMore = resolve
|
||||
}
|
||||
)
|
||||
let resolveLoadMore: (value: JobListItem[]) => void
|
||||
const loadMorePromise = new Promise<JobListItem[]>((resolve) => {
|
||||
resolveLoadMore = resolve
|
||||
})
|
||||
vi.mocked(api.getHistory).mockReturnValueOnce(loadMorePromise)
|
||||
|
||||
// Start first loadMore
|
||||
@@ -302,9 +249,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
|
||||
// Resolve
|
||||
const secondBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(200 + i)
|
||||
createMockJobItem(200 + i)
|
||||
)
|
||||
resolveLoadMore!({ History: secondBatch })
|
||||
resolveLoadMore!(secondBatch)
|
||||
|
||||
await Promise.all([firstLoad, secondLoad])
|
||||
|
||||
@@ -317,21 +264,17 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
|
||||
// Initial load
|
||||
const firstBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: firstBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(firstBatch)
|
||||
await store.updateHistory()
|
||||
|
||||
// Load additional batches
|
||||
for (let batch = 1; batch < BATCH_COUNT; batch++) {
|
||||
const items = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(batch * 200 + i)
|
||||
createMockJobItem(batch * 200 + i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: items
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(items)
|
||||
await store.loadMoreHistory()
|
||||
}
|
||||
|
||||
@@ -344,21 +287,17 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
it('should maintain date sorting after pagination', async () => {
|
||||
// First batch
|
||||
const firstBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: firstBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(firstBatch)
|
||||
|
||||
await store.updateHistory()
|
||||
|
||||
// Second batch
|
||||
const secondBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(200 + i)
|
||||
createMockJobItem(200 + i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: secondBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(secondBatch)
|
||||
|
||||
await store.loadMoreHistory()
|
||||
|
||||
@@ -375,11 +314,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
it('should preserve existing data when loadMore fails', async () => {
|
||||
// First successful load - full batch
|
||||
const firstBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: firstBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(firstBatch)
|
||||
|
||||
await store.updateHistory()
|
||||
expect(store.historyAssets).toHaveLength(200)
|
||||
@@ -399,11 +336,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
it('should clear error state on successful retry', async () => {
|
||||
// First load succeeds
|
||||
const firstBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: firstBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(firstBatch)
|
||||
|
||||
await store.updateHistory()
|
||||
|
||||
@@ -416,11 +351,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
|
||||
// Third load succeeds
|
||||
const thirdBatch = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(200 + i)
|
||||
createMockJobItem(200 + i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: thirdBatch
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(thirdBatch)
|
||||
|
||||
await store.loadMoreHistory()
|
||||
|
||||
@@ -447,11 +380,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
|
||||
for (let batch = 0; batch < batches; batch++) {
|
||||
const items = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(batch * 200 + i)
|
||||
createMockJobItem(batch * 200 + i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: items
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(items)
|
||||
|
||||
if (batch === 0) {
|
||||
await store.updateHistory()
|
||||
@@ -473,11 +404,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
// Load items beyond limit
|
||||
for (let batch = 0; batch < 6; batch++) {
|
||||
const items = Array.from({ length: 200 }, (_, i) =>
|
||||
createMockHistoryItem(batch * 200 + i)
|
||||
createMockJobItem(batch * 200 + i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce({
|
||||
History: items
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValueOnce(items)
|
||||
|
||||
if (batch === 0) {
|
||||
await store.updateHistory()
|
||||
@@ -500,11 +429,9 @@ describe('assetsStore - Refactored (Option A)', () => {
|
||||
describe('jobDetailView Support', () => {
|
||||
it('should include outputCount and allOutputs in user_metadata', async () => {
|
||||
const mockHistory = Array.from({ length: 5 }, (_, i) =>
|
||||
createMockHistoryItem(i)
|
||||
createMockJobItem(i)
|
||||
)
|
||||
vi.mocked(api.getHistory).mockResolvedValue({
|
||||
History: mockHistory
|
||||
})
|
||||
vi.mocked(api.getHistory).mockResolvedValue(mockHistory)
|
||||
|
||||
await store.updateHistory()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user