mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
lazy fetch exec error for dialog
This commit is contained in:
@@ -107,6 +107,7 @@ import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
|||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import { isCloud } from '@/platform/distribution/types'
|
import { isCloud } from '@/platform/distribution/types'
|
||||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||||
|
import { api } from '@/scripts/api'
|
||||||
import { useDialogService } from '@/services/dialogService'
|
import { useDialogService } from '@/services/dialogService'
|
||||||
import { useExecutionStore } from '@/stores/executionStore'
|
import { useExecutionStore } from '@/stores/executionStore'
|
||||||
import { useQueueStore } from '@/stores/queueStore'
|
import { useQueueStore } from '@/stores/queueStore'
|
||||||
@@ -354,6 +355,7 @@ const { errorMessageValue, copyErrorMessage, reportJobError } =
|
|||||||
useJobErrorReporting({
|
useJobErrorReporting({
|
||||||
taskForJob,
|
taskForJob,
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
dialog
|
dialog,
|
||||||
|
fetchApi: (url) => api.fetchApi(url)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { ComputedRef } from 'vue'
|
import type { ComputedRef } from 'vue'
|
||||||
|
|
||||||
|
import { fetchJobDetail } from '@/platform/remote/comfyui/jobs'
|
||||||
|
import type { ExecutionErrorWsMessage } from '@/schemas/apiSchema'
|
||||||
import type { TaskItemImpl } from '@/stores/queueStore'
|
import type { TaskItemImpl } from '@/stores/queueStore'
|
||||||
|
|
||||||
type CopyHandler = (value: string) => void | Promise<void>
|
type CopyHandler = (value: string) => void | Promise<void>
|
||||||
|
type FetchApi = (url: string) => Promise<Response>
|
||||||
|
|
||||||
export type JobErrorDialogService = {
|
export type JobErrorDialogService = {
|
||||||
showErrorDialog: (
|
showErrorDialog: (
|
||||||
@@ -13,18 +16,22 @@ export type JobErrorDialogService = {
|
|||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
) => void
|
) => void
|
||||||
|
showExecutionErrorDialog?: (executionError: ExecutionErrorWsMessage) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type UseJobErrorReportingOptions = {
|
type UseJobErrorReportingOptions = {
|
||||||
taskForJob: ComputedRef<TaskItemImpl | null>
|
taskForJob: ComputedRef<TaskItemImpl | null>
|
||||||
copyToClipboard: CopyHandler
|
copyToClipboard: CopyHandler
|
||||||
dialog: JobErrorDialogService
|
dialog: JobErrorDialogService
|
||||||
|
/** Optional fetch function to enable rich error dialogs with traceback */
|
||||||
|
fetchApi?: FetchApi
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useJobErrorReporting = ({
|
export const useJobErrorReporting = ({
|
||||||
taskForJob,
|
taskForJob,
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
dialog
|
dialog,
|
||||||
|
fetchApi
|
||||||
}: UseJobErrorReportingOptions) => {
|
}: UseJobErrorReportingOptions) => {
|
||||||
const errorMessageValue = computed(() => taskForJob.value?.errorMessage ?? '')
|
const errorMessageValue = computed(() => taskForJob.value?.errorMessage ?? '')
|
||||||
|
|
||||||
@@ -34,7 +41,26 @@ export const useJobErrorReporting = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const reportJobError = () => {
|
const reportJobError = async () => {
|
||||||
|
const task = taskForJob.value
|
||||||
|
if (!task) return
|
||||||
|
|
||||||
|
// Try to fetch rich error details if fetchApi is provided
|
||||||
|
if (fetchApi && dialog.showExecutionErrorDialog) {
|
||||||
|
const jobDetail = await fetchJobDetail(fetchApi, task.promptId)
|
||||||
|
const executionError = jobDetail?.execution_error
|
||||||
|
|
||||||
|
if (executionError) {
|
||||||
|
dialog.showExecutionErrorDialog({
|
||||||
|
prompt_id: task.promptId,
|
||||||
|
timestamp: jobDetail?.create_time ?? Date.now(),
|
||||||
|
...executionError
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to simple error dialog
|
||||||
if (errorMessageValue.value) {
|
if (errorMessageValue.value) {
|
||||||
dialog.showErrorDialog(new Error(errorMessageValue.value), {
|
dialog.showErrorDialog(new Error(errorMessageValue.value), {
|
||||||
reportType: 'queueJobError'
|
reportType: 'queueJobError'
|
||||||
|
|||||||
@@ -93,9 +93,26 @@ export function useJobMenu(
|
|||||||
if (message) await copyToClipboard(message)
|
if (message) await copyToClipboard(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const reportError = () => {
|
const reportError = async () => {
|
||||||
const item = currentMenuItem()
|
const item = currentMenuItem()
|
||||||
const message = item?.taskRef?.errorMessage
|
if (!item) return
|
||||||
|
|
||||||
|
// Try to fetch rich error details from job detail
|
||||||
|
const jobDetail = await fetchJobDetail((url) => api.fetchApi(url), item.id)
|
||||||
|
const executionError = jobDetail?.execution_error
|
||||||
|
|
||||||
|
if (executionError) {
|
||||||
|
// Use rich error dialog with traceback, node info, etc.
|
||||||
|
useDialogService().showExecutionErrorDialog({
|
||||||
|
prompt_id: item.id,
|
||||||
|
timestamp: jobDetail?.create_time ?? Date.now(),
|
||||||
|
...executionError
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to simple error dialog
|
||||||
|
const message = item.taskRef?.errorMessage
|
||||||
if (message) {
|
if (message) {
|
||||||
useDialogService().showErrorDialog(new Error(message), {
|
useDialogService().showErrorDialog(new Error(message), {
|
||||||
reportType: 'queueJobError'
|
reportType: 'queueJobError'
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import { zNodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
|
||||||
import { resultItemType, zTaskOutput } from '@/schemas/apiSchema'
|
import { resultItemType, zTaskOutput } from '@/schemas/apiSchema'
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -68,27 +67,19 @@ const zExtraData = z
|
|||||||
.passthrough()
|
.passthrough()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execution status information
|
* Execution error details for failed jobs.
|
||||||
|
* Contains the same structure as ExecutionErrorWsMessage from WebSocket.
|
||||||
*/
|
*/
|
||||||
const zExecutionStatus = z
|
const zExecutionError = z.object({
|
||||||
.object({
|
node_id: z.string(),
|
||||||
completed: z.boolean(),
|
node_type: z.string(),
|
||||||
messages: z.array(z.tuple([z.string(), z.unknown()])),
|
executed: z.array(z.string()),
|
||||||
status_str: z.string()
|
exception_message: z.string(),
|
||||||
})
|
exception_type: z.string(),
|
||||||
.passthrough()
|
traceback: z.array(z.string()),
|
||||||
|
current_inputs: z.unknown(),
|
||||||
/**
|
current_outputs: z.unknown()
|
||||||
* Execution metadata for a node
|
})
|
||||||
*/
|
|
||||||
const zExecutionNodeMeta = z
|
|
||||||
.object({
|
|
||||||
node_id: zNodeId,
|
|
||||||
display_node: zNodeId,
|
|
||||||
parent_node: zNodeId.nullable(),
|
|
||||||
real_node_id: zNodeId
|
|
||||||
})
|
|
||||||
.passthrough()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Job detail - returned by GET /api/jobs/{job_id} (detail endpoint)
|
* Job detail - returned by GET /api/jobs/{job_id} (detail endpoint)
|
||||||
@@ -101,8 +92,9 @@ export const zJobDetail = zRawJobListItem
|
|||||||
extra_data: zExtraData.optional(),
|
extra_data: zExtraData.optional(),
|
||||||
prompt: z.record(z.string(), z.unknown()).optional(),
|
prompt: z.record(z.string(), z.unknown()).optional(),
|
||||||
outputs: zTaskOutput.optional(),
|
outputs: zTaskOutput.optional(),
|
||||||
execution_status: zExecutionStatus.optional(),
|
execution_time: z.number().optional(),
|
||||||
execution_meta: z.record(z.string(), zExecutionNodeMeta).optional()
|
workflow_id: z.string().nullable().optional(),
|
||||||
|
execution_error: zExecutionError.nullable().optional()
|
||||||
})
|
})
|
||||||
.passthrough()
|
.passthrough()
|
||||||
|
|
||||||
@@ -124,4 +116,3 @@ export type JobStatus = z.infer<typeof zJobStatus>
|
|||||||
export type RawJobListItem = z.infer<typeof zRawJobListItem>
|
export type RawJobListItem = z.infer<typeof zRawJobListItem>
|
||||||
export type JobListItem = z.infer<typeof zJobListItem>
|
export type JobListItem = z.infer<typeof zJobListItem>
|
||||||
export type JobDetail = z.infer<typeof zJobDetail>
|
export type JobDetail = z.infer<typeof zJobDetail>
|
||||||
export type ExecutionStatus = z.infer<typeof zExecutionStatus>
|
|
||||||
|
|||||||
@@ -6,23 +6,34 @@ import type { TaskItemImpl } from '@/stores/queueStore'
|
|||||||
import type { JobErrorDialogService } from '@/components/queue/job/useJobErrorReporting'
|
import type { JobErrorDialogService } from '@/components/queue/job/useJobErrorReporting'
|
||||||
import { useJobErrorReporting } from '@/components/queue/job/useJobErrorReporting'
|
import { useJobErrorReporting } from '@/components/queue/job/useJobErrorReporting'
|
||||||
|
|
||||||
const createTaskWithError = (errorMessage?: string): TaskItemImpl =>
|
const fetchJobDetailMock = vi.fn()
|
||||||
({ errorMessage }) as unknown as TaskItemImpl
|
vi.mock('@/platform/remote/comfyui/jobs', () => ({
|
||||||
|
fetchJobDetail: (...args: unknown[]) => fetchJobDetailMock(...args)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const createTaskWithError = (
|
||||||
|
promptId: string,
|
||||||
|
errorMessage?: string
|
||||||
|
): TaskItemImpl => ({ promptId, errorMessage }) as unknown as TaskItemImpl
|
||||||
|
|
||||||
describe('useJobErrorReporting', () => {
|
describe('useJobErrorReporting', () => {
|
||||||
let taskState = ref<TaskItemImpl | null>(null)
|
let taskState = ref<TaskItemImpl | null>(null)
|
||||||
let taskForJob: ComputedRef<TaskItemImpl | null>
|
let taskForJob: ComputedRef<TaskItemImpl | null>
|
||||||
let copyToClipboard: ReturnType<typeof vi.fn>
|
let copyToClipboard: ReturnType<typeof vi.fn>
|
||||||
let showErrorDialog: ReturnType<typeof vi.fn>
|
let showErrorDialog: ReturnType<typeof vi.fn>
|
||||||
|
let showExecutionErrorDialog: ReturnType<typeof vi.fn>
|
||||||
let dialog: JobErrorDialogService
|
let dialog: JobErrorDialogService
|
||||||
let composable: ReturnType<typeof useJobErrorReporting>
|
let composable: ReturnType<typeof useJobErrorReporting>
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
taskState = ref<TaskItemImpl | null>(null)
|
taskState = ref<TaskItemImpl | null>(null)
|
||||||
taskForJob = computed(() => taskState.value)
|
taskForJob = computed(() => taskState.value)
|
||||||
copyToClipboard = vi.fn()
|
copyToClipboard = vi.fn()
|
||||||
showErrorDialog = vi.fn()
|
showErrorDialog = vi.fn()
|
||||||
dialog = { showErrorDialog }
|
showExecutionErrorDialog = vi.fn()
|
||||||
|
dialog = { showErrorDialog, showExecutionErrorDialog }
|
||||||
|
fetchJobDetailMock.mockResolvedValue(undefined)
|
||||||
composable = useJobErrorReporting({
|
composable = useJobErrorReporting({
|
||||||
taskForJob,
|
taskForJob,
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
@@ -35,34 +46,35 @@ describe('useJobErrorReporting', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('exposes a computed message that reflects the current task error', () => {
|
it('exposes a computed message that reflects the current task error', () => {
|
||||||
taskState.value = createTaskWithError('First failure')
|
taskState.value = createTaskWithError('job-1', 'First failure')
|
||||||
expect(composable.errorMessageValue.value).toBe('First failure')
|
expect(composable.errorMessageValue.value).toBe('First failure')
|
||||||
|
|
||||||
taskState.value = createTaskWithError('Second failure')
|
taskState.value = createTaskWithError('job-2', 'Second failure')
|
||||||
expect(composable.errorMessageValue.value).toBe('Second failure')
|
expect(composable.errorMessageValue.value).toBe('Second failure')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns empty string when no error message', () => {
|
it('returns empty string when no error message', () => {
|
||||||
taskState.value = createTaskWithError()
|
taskState.value = createTaskWithError('job-1')
|
||||||
expect(composable.errorMessageValue.value).toBe('')
|
expect(composable.errorMessageValue.value).toBe('')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('only calls the copy handler when a message exists', () => {
|
it('only calls the copy handler when a message exists', () => {
|
||||||
taskState.value = createTaskWithError('Clipboard failure')
|
taskState.value = createTaskWithError('job-1', 'Clipboard failure')
|
||||||
composable.copyErrorMessage()
|
composable.copyErrorMessage()
|
||||||
expect(copyToClipboard).toHaveBeenCalledTimes(1)
|
expect(copyToClipboard).toHaveBeenCalledTimes(1)
|
||||||
expect(copyToClipboard).toHaveBeenCalledWith('Clipboard failure')
|
expect(copyToClipboard).toHaveBeenCalledWith('Clipboard failure')
|
||||||
|
|
||||||
copyToClipboard.mockClear()
|
copyToClipboard.mockClear()
|
||||||
taskState.value = createTaskWithError()
|
taskState.value = createTaskWithError('job-2')
|
||||||
composable.copyErrorMessage()
|
composable.copyErrorMessage()
|
||||||
expect(copyToClipboard).not.toHaveBeenCalled()
|
expect(copyToClipboard).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows error dialog with the error message', () => {
|
it('shows simple error dialog when no fetchApi provided', async () => {
|
||||||
taskState.value = createTaskWithError('Queue job error')
|
taskState.value = createTaskWithError('job-1', 'Queue job error')
|
||||||
composable.reportJobError()
|
await composable.reportJobError()
|
||||||
|
|
||||||
|
expect(fetchJobDetailMock).not.toHaveBeenCalled()
|
||||||
expect(showErrorDialog).toHaveBeenCalledTimes(1)
|
expect(showErrorDialog).toHaveBeenCalledTimes(1)
|
||||||
const [errorArg, optionsArg] = showErrorDialog.mock.calls[0]
|
const [errorArg, optionsArg] = showErrorDialog.mock.calls[0]
|
||||||
expect(errorArg).toBeInstanceOf(Error)
|
expect(errorArg).toBeInstanceOf(Error)
|
||||||
@@ -70,9 +82,95 @@ describe('useJobErrorReporting', () => {
|
|||||||
expect(optionsArg).toEqual({ reportType: 'queueJobError' })
|
expect(optionsArg).toEqual({ reportType: 'queueJobError' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does nothing when no error message exists', () => {
|
it('does nothing when no task exists', async () => {
|
||||||
taskState.value = createTaskWithError()
|
taskState.value = null
|
||||||
composable.reportJobError()
|
await composable.reportJobError()
|
||||||
expect(showErrorDialog).not.toHaveBeenCalled()
|
expect(showErrorDialog).not.toHaveBeenCalled()
|
||||||
|
expect(showExecutionErrorDialog).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with fetchApi provided', () => {
|
||||||
|
let fetchApi: ReturnType<typeof vi.fn>
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fetchApi = vi.fn()
|
||||||
|
composable = useJobErrorReporting({
|
||||||
|
taskForJob,
|
||||||
|
copyToClipboard,
|
||||||
|
dialog,
|
||||||
|
fetchApi
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows rich error dialog when execution_error available', async () => {
|
||||||
|
const executionError = {
|
||||||
|
node_id: '5',
|
||||||
|
node_type: 'KSampler',
|
||||||
|
executed: ['1', '2'],
|
||||||
|
exception_message: 'CUDA out of memory',
|
||||||
|
exception_type: 'RuntimeError',
|
||||||
|
traceback: ['line 1', 'line 2'],
|
||||||
|
current_inputs: {},
|
||||||
|
current_outputs: {}
|
||||||
|
}
|
||||||
|
fetchJobDetailMock.mockResolvedValue({
|
||||||
|
id: 'job-1',
|
||||||
|
create_time: 12345,
|
||||||
|
execution_error: executionError
|
||||||
|
})
|
||||||
|
taskState.value = createTaskWithError('job-1', 'CUDA out of memory')
|
||||||
|
|
||||||
|
await composable.reportJobError()
|
||||||
|
|
||||||
|
expect(fetchJobDetailMock).toHaveBeenCalledWith(fetchApi, 'job-1')
|
||||||
|
expect(showExecutionErrorDialog).toHaveBeenCalledTimes(1)
|
||||||
|
expect(showExecutionErrorDialog).toHaveBeenCalledWith({
|
||||||
|
prompt_id: 'job-1',
|
||||||
|
timestamp: 12345,
|
||||||
|
...executionError
|
||||||
|
})
|
||||||
|
expect(showErrorDialog).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('falls back to simple error dialog when no execution_error', async () => {
|
||||||
|
fetchJobDetailMock.mockResolvedValue({
|
||||||
|
id: 'job-1',
|
||||||
|
execution_error: null
|
||||||
|
})
|
||||||
|
taskState.value = createTaskWithError('job-1', 'Job failed')
|
||||||
|
|
||||||
|
await composable.reportJobError()
|
||||||
|
|
||||||
|
expect(fetchJobDetailMock).toHaveBeenCalledWith(fetchApi, 'job-1')
|
||||||
|
expect(showExecutionErrorDialog).not.toHaveBeenCalled()
|
||||||
|
expect(showErrorDialog).toHaveBeenCalledTimes(1)
|
||||||
|
const [errorArg, optionsArg] = showErrorDialog.mock.calls[0]
|
||||||
|
expect(errorArg).toBeInstanceOf(Error)
|
||||||
|
expect(errorArg.message).toBe('Job failed')
|
||||||
|
expect(optionsArg).toEqual({ reportType: 'queueJobError' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('falls back to simple error dialog when fetch fails', async () => {
|
||||||
|
fetchJobDetailMock.mockResolvedValue(undefined)
|
||||||
|
taskState.value = createTaskWithError('job-1', 'Job failed')
|
||||||
|
|
||||||
|
await composable.reportJobError()
|
||||||
|
|
||||||
|
expect(showExecutionErrorDialog).not.toHaveBeenCalled()
|
||||||
|
expect(showErrorDialog).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does nothing when no error message and no execution_error', async () => {
|
||||||
|
fetchJobDetailMock.mockResolvedValue({
|
||||||
|
id: 'job-1',
|
||||||
|
execution_error: null
|
||||||
|
})
|
||||||
|
taskState.value = createTaskWithError('job-1')
|
||||||
|
|
||||||
|
await composable.reportJobError()
|
||||||
|
|
||||||
|
expect(showErrorDialog).not.toHaveBeenCalled()
|
||||||
|
expect(showExecutionErrorDialog).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ vi.mock('@/scripts/utils', () => ({
|
|||||||
|
|
||||||
const dialogServiceMock = {
|
const dialogServiceMock = {
|
||||||
showErrorDialog: vi.fn(),
|
showErrorDialog: vi.fn(),
|
||||||
|
showExecutionErrorDialog: vi.fn(),
|
||||||
prompt: vi.fn()
|
prompt: vi.fn()
|
||||||
}
|
}
|
||||||
vi.mock('@/services/dialogService', () => ({
|
vi.mock('@/services/dialogService', () => ({
|
||||||
@@ -287,7 +288,52 @@ describe('useJobMenu', () => {
|
|||||||
expect(copyToClipboardMock).toHaveBeenCalledWith('Something went wrong')
|
expect(copyToClipboardMock).toHaveBeenCalledWith('Something went wrong')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('reports error via dialog when entry triggered', async () => {
|
it('reports error via rich dialog when execution_error available', async () => {
|
||||||
|
const executionError = {
|
||||||
|
node_id: '5',
|
||||||
|
node_type: 'KSampler',
|
||||||
|
executed: ['1', '2'],
|
||||||
|
exception_message: 'CUDA out of memory',
|
||||||
|
exception_type: 'RuntimeError',
|
||||||
|
traceback: ['line 1', 'line 2'],
|
||||||
|
current_inputs: {},
|
||||||
|
current_outputs: {}
|
||||||
|
}
|
||||||
|
fetchJobDetailMock.mockResolvedValue({
|
||||||
|
id: 'job-1',
|
||||||
|
create_time: 12345,
|
||||||
|
execution_error: executionError
|
||||||
|
})
|
||||||
|
const { jobMenuEntries } = mountJobMenu()
|
||||||
|
setCurrentItem(
|
||||||
|
createJobItem({
|
||||||
|
state: 'failed',
|
||||||
|
taskRef: { errorMessage: 'CUDA out of memory' } as any
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
const entry = findActionEntry(jobMenuEntries.value, 'report-error')
|
||||||
|
await entry?.onClick?.()
|
||||||
|
|
||||||
|
expect(fetchJobDetailMock).toHaveBeenCalledWith(
|
||||||
|
expect.any(Function),
|
||||||
|
'job-1'
|
||||||
|
)
|
||||||
|
expect(dialogServiceMock.showExecutionErrorDialog).toHaveBeenCalledTimes(1)
|
||||||
|
expect(dialogServiceMock.showExecutionErrorDialog).toHaveBeenCalledWith({
|
||||||
|
prompt_id: 'job-1',
|
||||||
|
timestamp: 12345,
|
||||||
|
...executionError
|
||||||
|
})
|
||||||
|
expect(dialogServiceMock.showErrorDialog).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('falls back to simple error dialog when no execution_error', async () => {
|
||||||
|
fetchJobDetailMock.mockResolvedValue({
|
||||||
|
id: 'job-1',
|
||||||
|
execution_error: null
|
||||||
|
})
|
||||||
const { jobMenuEntries } = mountJobMenu()
|
const { jobMenuEntries } = mountJobMenu()
|
||||||
setCurrentItem(
|
setCurrentItem(
|
||||||
createJobItem({
|
createJobItem({
|
||||||
@@ -298,8 +344,9 @@ describe('useJobMenu', () => {
|
|||||||
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
const entry = findActionEntry(jobMenuEntries.value, 'report-error')
|
const entry = findActionEntry(jobMenuEntries.value, 'report-error')
|
||||||
entry?.onClick?.()
|
await entry?.onClick?.()
|
||||||
|
|
||||||
|
expect(dialogServiceMock.showExecutionErrorDialog).not.toHaveBeenCalled()
|
||||||
expect(dialogServiceMock.showErrorDialog).toHaveBeenCalledTimes(1)
|
expect(dialogServiceMock.showErrorDialog).toHaveBeenCalledTimes(1)
|
||||||
const [errorArg, optionsArg] =
|
const [errorArg, optionsArg] =
|
||||||
dialogServiceMock.showErrorDialog.mock.calls[0]
|
dialogServiceMock.showErrorDialog.mock.calls[0]
|
||||||
@@ -309,6 +356,7 @@ describe('useJobMenu', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('ignores error actions when message missing', async () => {
|
it('ignores error actions when message missing', async () => {
|
||||||
|
fetchJobDetailMock.mockResolvedValue({ id: 'job-1', execution_error: null })
|
||||||
const { jobMenuEntries } = mountJobMenu()
|
const { jobMenuEntries } = mountJobMenu()
|
||||||
setCurrentItem(
|
setCurrentItem(
|
||||||
createJobItem({
|
createJobItem({
|
||||||
@@ -325,6 +373,7 @@ describe('useJobMenu', () => {
|
|||||||
|
|
||||||
expect(copyToClipboardMock).not.toHaveBeenCalled()
|
expect(copyToClipboardMock).not.toHaveBeenCalled()
|
||||||
expect(dialogServiceMock.showErrorDialog).not.toHaveBeenCalled()
|
expect(dialogServiceMock.showErrorDialog).not.toHaveBeenCalled()
|
||||||
|
expect(dialogServiceMock.showExecutionErrorDialog).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
const previewCases = [
|
const previewCases = [
|
||||||
|
|||||||
Reference in New Issue
Block a user