fix: surface cancel errors, fix SSE sentinel scroll, add missing test coverage in TrainJobsView
This commit is contained in:
parent
e014da2dec
commit
53b25b27ab
2 changed files with 47 additions and 2 deletions
|
|
@ -124,4 +124,38 @@ describe('TrainJobsView', () => {
|
|||
await flushPromises()
|
||||
expect(w.find('button.view-log-btn').exists()).toBe(true)
|
||||
})
|
||||
it('shows error when config JSON is invalid', async () => {
|
||||
const w = mount(TrainJobsView)
|
||||
await flushPromises()
|
||||
await w.find('input.model-key-input').setValue('my-model')
|
||||
await w.find('textarea.config-textarea').setValue('{ not valid json }')
|
||||
await w.find('button.submit-job-btn').trigger('click')
|
||||
await flushPromises()
|
||||
expect(w.find('.error-notice').exists()).toBe(true)
|
||||
expect(w.find('.error-notice').text()).toContain('not valid')
|
||||
})
|
||||
|
||||
it('shows error notice when jobs load fails', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 500,
|
||||
json: async () => ({}),
|
||||
text: async () => '',
|
||||
}))
|
||||
const w = mount(TrainJobsView)
|
||||
await flushPromises()
|
||||
expect(w.find('.error-notice').exists()).toBe(true)
|
||||
expect(w.find('table.jobs-table').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('cancel button optimistically updates job status to cancelled', async () => {
|
||||
const w = mount(TrainJobsView)
|
||||
await flushPromises()
|
||||
await w.find('button.cancel-btn').trigger('click')
|
||||
await flushPromises()
|
||||
// After cancel, job should show status-cancelled pill (not status-queued)
|
||||
expect(w.find('.status-cancelled').exists()).toBe(true)
|
||||
expect(w.find('.status-queued').exists()).toBe(false)
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="cancelError" class="error-notice" role="alert">{{ cancelError }}</div>
|
||||
</section>
|
||||
|
||||
<!-- Log panel (SSE) -->
|
||||
|
|
@ -151,6 +152,7 @@ const loadError = ref<string | null>(null)
|
|||
const submitError = ref<string | null>(null)
|
||||
const submitting = ref(false)
|
||||
const cancellingId = ref<string | null>(null)
|
||||
const cancelError = ref<string | null>(null)
|
||||
|
||||
const form = ref({
|
||||
type: 'classifier' as 'classifier' | 'llm-sft',
|
||||
|
|
@ -225,15 +227,19 @@ async function submitJob() {
|
|||
|
||||
async function cancelJob(id: string) {
|
||||
cancellingId.value = id
|
||||
cancelError.value = null
|
||||
try {
|
||||
const res = await fetch(`/api/train/jobs/${encodeURIComponent(id)}/cancel`, { method: 'DELETE' })
|
||||
if (res.ok) {
|
||||
jobs.value = jobs.value.map(j =>
|
||||
j.id === id ? { ...j, status: 'cancelled' as const } : j
|
||||
)
|
||||
} else {
|
||||
cancelError.value = `Failed to cancel job (HTTP ${res.status}).`
|
||||
}
|
||||
} catch { /* non-fatal */ }
|
||||
finally {
|
||||
} catch {
|
||||
cancelError.value = 'Network error cancelling job.'
|
||||
} finally {
|
||||
cancellingId.value = null
|
||||
}
|
||||
}
|
||||
|
|
@ -258,6 +264,11 @@ function openLog(id: string) {
|
|||
}
|
||||
if (data.type === 'error') {
|
||||
logLines.value = [...logLines.value, '--- stream ended with error ---']
|
||||
nextTick(() => {
|
||||
if (logPanelEl.value) {
|
||||
logPanelEl.value.scrollTop = logPanelEl.value.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
() => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue