Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
71c6da2
Readd cancel button
benceruleanlu Dec 6, 2025
b790aaf
Add new N queued toggle
benceruleanlu Dec 6, 2025
e65fc56
Remove active state
benceruleanlu Dec 7, 2025
ca084c3
Fix text size
benceruleanlu Dec 7, 2025
5c106bd
Add progress bar embedded into top menu section
benceruleanlu Dec 7, 2025
da4b241
Remove unused QueueOverlayActive files
benceruleanlu Dec 7, 2025
5e1dc91
Extract to own component file
benceruleanlu Dec 7, 2025
d965fb0
Add progress bars and progress text
benceruleanlu Dec 7, 2025
05d2cae
Add close button back to queue progress overlay
benceruleanlu Dec 7, 2025
99019cf
Align expanded QPO to new design
benceruleanlu Dec 7, 2025
0a5fc8b
Delete unused file
benceruleanlu Dec 7, 2025
c4b53c1
Make job list button visual seperation change
benceruleanlu Dec 8, 2025
48aa3d9
Remove unused event
benceruleanlu Dec 8, 2025
87abea9
Merge remote-tracking branch 'origin/main' into bl-semantic-jellyfish
benceruleanlu Dec 9, 2025
ad44241
[automated] Update test expectations
invalid-email-address Dec 9, 2025
73741df
fix: lint issues and unused exports
benceruleanlu Dec 9, 2025
8b4f174
refactor(actionbar): move cancel and queue buttons into draggable con…
benceruleanlu Dec 9, 2025
f0b9d28
fix(queue): rename clearQueued translation key to clearQueue
benceruleanlu Dec 9, 2025
b428892
Add tests
benceruleanlu Dec 9, 2025
ab820bf
Readd missing DTID
benceruleanlu Dec 9, 2025
f6ffb1f
Fix actionbar digit shifting
benceruleanlu Dec 9, 2025
db80292
Fix text expectation after digit shift change
benceruleanlu Dec 9, 2025
a5b5fe8
Merge remote-tracking branch 'origin/main' into bl-semantic-jellyfish
benceruleanlu Dec 9, 2025
a48360d
nit
benceruleanlu Dec 9, 2025
96893f7
nit
benceruleanlu Dec 9, 2025
cbbf3eb
[automated] Update test expectations
invalid-email-address Dec 9, 2025
f7d3a8c
nit
benceruleanlu Dec 9, 2025
8fdf355
nit
benceruleanlu Dec 9, 2025
38b589c
nit
benceruleanlu Dec 9, 2025
3ce8cca
nit
benceruleanlu Dec 9, 2025
3fe2d39
nit
benceruleanlu Dec 9, 2025
ae1d6f9
nit
benceruleanlu Dec 9, 2025
b277cbf
nit
benceruleanlu Dec 9, 2025
b21cf86
Merge branch 'bl-semantic-jellyfish' of https://github.com/Comfy-Org/…
benceruleanlu Dec 9, 2025
8bec2df
nit
benceruleanlu Dec 9, 2025
268ab1d
Implement progress bar on actionbar
benceruleanlu Dec 10, 2025
36fda56
Implement progress text on actionbar
benceruleanlu Dec 10, 2025
33626a5
Fix menu position docked issue
benceruleanlu Dec 10, 2025
d963b8e
Widen drop zone
benceruleanlu Dec 10, 2025
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
Prev Previous commit
Next Next commit
nit
  • Loading branch information
benceruleanlu committed Dec 9, 2025
commit ae1d6f9021e589a0dc2401cd666d04174f7df006
80 changes: 6 additions & 74 deletions src/composables/queue/useJobList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n'
import { useCurrentNodeName } from '@/composables/queue/useCurrentNodeName'
import { useQueueProgress } from '@/composables/queue/useQueueProgress'
import { isCloud } from '@/platform/distribution/types'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useQueueStore } from '@/stores/queueStore'
import type { TaskItemImpl } from '@/stores/queueStore'
Expand All @@ -19,14 +18,6 @@ import {
import { buildJobDisplay } from '@/utils/queueDisplay'
import { jobStateFromTask } from '@/utils/queueUtil'

/** Tabs for job list filtering */
/** Tabs for job list filtering */
const jobTabs = ['All', 'Completed', 'Failed']
type JobTab = (typeof jobTabs)[number]

const jobSortModes = ['mostRecent', 'totalGenerationTime']
type JobSortMode = (typeof jobSortModes)[number]

/**
* UI item in the job list. Mirrors data previously prepared inline.
*/
Expand Down Expand Up @@ -89,13 +80,12 @@ type TaskWithState = {
}

/**
* Builds the reactive job list, filters, and grouped view for the queue overlay.
* Builds the reactive job list and grouped view for the queue overlay.
*/
export function useJobList() {
const { t, locale } = useI18n()
const queueStore = useQueueStore()
const executionStore = useExecutionStore()
const workflowStore = useWorkflowStore()

const seenPendingIds = ref<Set<string>>(new Set())
const recentlyAddedPendingIds = ref<Set<string>>(new Set())
Expand Down Expand Up @@ -184,11 +174,7 @@ export function useJobList() {
const isJobInitializing = (promptId: string | number | undefined) =>
executionStore.isPromptInitializing(promptId)

const selectedJobTab = ref<JobTab>('All')
const selectedWorkflowFilter = ref<'all' | 'current'>('all')
const selectedSortMode = ref<JobSortMode>('mostRecent')

const allTasksSorted = computed<TaskItemImpl[]>(() => {
const orderedTasks = computed<TaskItemImpl[]>(() => {
const all = [
...queueStore.pendingTasks,
...queueStore.runningTasks,
Expand All @@ -198,50 +184,14 @@ export function useJobList() {
})

const tasksWithJobState = computed<TaskWithState[]>(() =>
allTasksSorted.value.map((task) => ({
orderedTasks.value.map((task) => ({
task,
state: jobStateFromTask(task, isJobInitializing(task?.promptId))
}))
)

const hasFailedJobs = computed(() =>
tasksWithJobState.value.some(({ state }) => state === 'failed')
)

watch(
() => hasFailedJobs.value,
(hasFailed) => {
if (!hasFailed && selectedJobTab.value === 'Failed') {
selectedJobTab.value = 'All'
}
}
)

const filteredTaskEntries = computed<TaskWithState[]>(() => {
let entries = tasksWithJobState.value
if (selectedJobTab.value === 'Completed') {
entries = entries.filter(({ state }) => state === 'completed')
} else if (selectedJobTab.value === 'Failed') {
entries = entries.filter(({ state }) => state === 'failed')
}

if (selectedWorkflowFilter.value === 'current') {
const activeId = workflowStore.activeWorkflow?.activeState?.id
if (!activeId) return []
entries = entries.filter(({ task }) => {
const wid = task.workflow?.id
return !!wid && wid === activeId
})
}
return entries
})

const filteredTasks = computed<TaskItemImpl[]>(() =>
filteredTaskEntries.value.map(({ task }) => task)
)

const jobItems = computed<JobListItem[]>(() => {
return filteredTaskEntries.value.map(({ task, state }) => {
return tasksWithJobState.value.map(({ task, state }) => {
const isActive =
String(task.promptId ?? '') ===
String(executionStore.activePromptId ?? '')
Expand Down Expand Up @@ -295,7 +245,7 @@ export function useJobList() {
const groups: JobGroup[] = []
const index = new Map<string, number>()
const localeValue = locale.value
for (const { task, state } of filteredTaskEntries.value) {
for (const { task, state } of tasksWithJobState.value) {
let ts: number | undefined
if (state === 'completed' || state === 'failed') {
ts = task.executionEndTimestamp
Expand All @@ -321,29 +271,11 @@ export function useJobList() {
if (ji) groups[groupIdx].items.push(ji)
}

if (selectedSortMode.value === 'totalGenerationTime') {
const valueOrDefault = (value: JobListItem['executionTimeMs']) =>
typeof value === 'number' && !Number.isNaN(value) ? value : -1
const sortByExecutionTimeDesc = (a: JobListItem, b: JobListItem) =>
valueOrDefault(b.executionTimeMs) - valueOrDefault(a.executionTimeMs)

groups.forEach((group) => {
group.items.sort(sortByExecutionTimeDesc)
})
}

return groups
})

return {
// filters/state
selectedJobTab,
selectedWorkflowFilter,
selectedSortMode,
hasFailedJobs,
// data sources
allTasksSorted,
filteredTasks,
orderedTasks,
jobItems,
groupedJobItems,
currentNodeName
Expand Down
92 changes: 6 additions & 86 deletions tests-ui/tests/composables/useJobList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,23 +158,6 @@ vi.mock('@/stores/executionStore', () => ({
}
}))

let workflowStoreMock: {
activeWorkflow: null | { activeState?: { id?: string } }
}
const ensureWorkflowStore = () => {
if (!workflowStoreMock) {
workflowStoreMock = reactive({
activeWorkflow: null as null | { activeState?: { id?: string } }
})
}
return workflowStoreMock
}
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
useWorkflowStore: () => {
return ensureWorkflowStore()
}
}))

const createTask = (
overrides: Partial<TestTask> & { mockState?: JobState } = {}
): TestTask => ({
Expand Down Expand Up @@ -210,9 +193,6 @@ const resetStores = () => {
executionStore.activePromptId = null
executionStore.executingNode = null

const workflowStore = ensureWorkflowStore()
workflowStore.activeWorkflow = null

ensureProgressRefs()
totalPercent.value = 0
currentNodePercent.value = 0
Expand Down Expand Up @@ -332,7 +312,7 @@ describe('useJobList', () => {
expect(vi.getTimerCount()).toBe(0)
})

it('sorts all tasks by queue index descending', async () => {
it('sorts tasks by queue index descending', async () => {
queueStoreMock.pendingTasks = [
createTask({ promptId: 'p', queueIndex: 1, mockState: 'pending' })
]
Expand All @@ -343,75 +323,16 @@ describe('useJobList', () => {
createTask({ promptId: 'h', queueIndex: 3, mockState: 'completed' })
]

const { allTasksSorted } = initComposable()
const { orderedTasks } = initComposable()
await flush()

expect(allTasksSorted.value.map((task) => task.promptId)).toEqual([
expect(orderedTasks.value.map((task) => task.promptId)).toEqual([
'r',
'h',
'p'
])
})

it('filters by job tab and resets failed tab when failures disappear', async () => {
queueStoreMock.historyTasks = [
createTask({ promptId: 'c', queueIndex: 3, mockState: 'completed' }),
createTask({ promptId: 'f', queueIndex: 2, mockState: 'failed' }),
createTask({ promptId: 'p', queueIndex: 1, mockState: 'pending' })
]

const instance = initComposable()
await flush()

instance.selectedJobTab.value = 'Completed'
await flush()
expect(instance.filteredTasks.value.map((t) => t.promptId)).toEqual(['c'])

instance.selectedJobTab.value = 'Failed'
await flush()
expect(instance.filteredTasks.value.map((t) => t.promptId)).toEqual(['f'])
expect(instance.hasFailedJobs.value).toBe(true)

queueStoreMock.historyTasks = [
createTask({ promptId: 'c', queueIndex: 3, mockState: 'completed' })
]
await flush()

expect(instance.hasFailedJobs.value).toBe(false)
expect(instance.selectedJobTab.value).toBe('All')
})

it('filters by active workflow when requested', async () => {
queueStoreMock.pendingTasks = [
createTask({
promptId: 'wf-1',
queueIndex: 2,
mockState: 'pending',
workflow: { id: 'workflow-1' }
}),
createTask({
promptId: 'wf-2',
queueIndex: 1,
mockState: 'pending',
workflow: { id: 'workflow-2' }
})
]

const instance = initComposable()
await flush()

instance.selectedWorkflowFilter.value = 'current'
await flush()
expect(instance.filteredTasks.value).toEqual([])

workflowStoreMock.activeWorkflow = { activeState: { id: 'workflow-1' } }
await flush()

expect(instance.filteredTasks.value.map((t) => t.promptId)).toEqual([
'wf-1'
])
})

it('hydrates job items with active progress and compute hours', async () => {
queueStoreMock.runningTasks = [
createTask({
Expand Down Expand Up @@ -468,7 +389,7 @@ describe('useJobList', () => {
)
})

it('groups job items by date label and sorts by total generation time when requested', async () => {
it('groups job items by date label using queue order', async () => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2024-01-10T12:00:00Z'))
queueStoreMock.historyTasks = [
Expand Down Expand Up @@ -501,7 +422,6 @@ describe('useJobList', () => {
]

const instance = initComposable()
instance.selectedSortMode.value = 'totalGenerationTime'
await flush()

const groups = instance.groupedJobItems.value
Expand All @@ -513,8 +433,8 @@ describe('useJobList', () => {

const todayGroup = groups[0]
expect(todayGroup.items.map((item) => item.id)).toEqual([
'today-large',
'today-small'
'today-small',
'today-large'
])
})
})