Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions changelog.d/workflow-paused-run-label.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A run frozen by a Stop that could not release its Bot is now named **Paused** wherever it is read: the stage strip says `Paused` instead of the `Pause` action's name, and the Runs tab keeps the run's `paused` flag instead of drawing the frozen run as `Running` while it hands nothing on.
11 changes: 7 additions & 4 deletions plugins/workflow/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ window.__ModuleLoader__.load({
'Show {count} more Bots': '展开另外 {count} 个 Bot',
'Show {count} more held tasks': '展开另外持有中的 {count} 个任务',
'Showing {shown} of {count} held tasks': '共有 {count} 个持有中的任务,当前显示 {shown} 个',
Paused: '已暂停',
// Stage timeline (#1489). The key is the English line; service.js and
// narrate.js keep the Chinese text only as the fallback for rows written
// before the key existed.
Expand Down Expand Up @@ -520,11 +521,11 @@ window.__ModuleLoader__.load({
function RunStrip({ workflow, run, focused, locked, onFocus, onPause, onResume, onStop, onTab, onOpenDrawer }) {
const running = (run.running ?? []).map(id => nodeOf(workflow, id)).filter(Boolean)
const chipClass = run.status === 'done' ? 'done' : (run.status === 'failed' ? 'failed' : (run.paused ? 'queued' : (running[0] ? 'running' : 'queued')))
const chipLabel = run.status === 'done' ? tr('Done') : (run.status === 'failed' ? tr('Failed') : (run.paused ? tr('Pause') : (running[0] ? tr('Running') : tr('Queued'))))
const chipLabel = run.status === 'done' ? tr('Done') : (run.status === 'failed' ? tr('Failed') : (run.paused ? tr('Paused') : (running[0] ? tr('Running') : tr('Queued'))))
const meta = run.status === 'failed'
? failureText(run)
: run.paused
? `${tr('Pause')} · ${running.map(node => node.name).join(' ‖ ') || '—'} · ${run.doneCount}/${run.nodeCount}`
? `${tr('Paused')} · ${running.map(node => node.name).join(' ‖ ') || '—'} · ${run.doneCount}/${run.nodeCount}`
: (running[0]
? `${tr('Running')} · ${running.map(node => node.name).join(' ‖ ')} · ${run.doneCount}/${run.nodeCount}`
: (run.status === 'done' ? tr('Done') : tr('Waiting for a task')))
Expand Down Expand Up @@ -1065,6 +1066,7 @@ window.__ModuleLoader__.load({
live: true,
task: run.task,
status: run.status,
paused: run.paused === true,
at: run.updatedAt,
failure: run.failure,
current: workflow.run?.id === run.id,
Expand Down Expand Up @@ -1390,10 +1392,11 @@ window.__ModuleLoader__.load({
// #1618: keep HH:MM in its own non-shrinking column with a gap before the
// status chip, and paint Failed once — skip wf-hist-reason when it only
// repeats the chip label (generic Failed with no host detail).
const chipLabel = tr(STATUS_KEY[item.status] ?? 'Queued')
const chipLabel = item.paused === true ? tr('Paused') : tr(STATUS_KEY[item.status] ?? 'Queued')
const reason = item.status === 'failed' ? failureText(item) : ''
const showReason = Boolean(reason) && reason !== chipLabel
const chipClass = item.status === 'done' ? 'done'
const chipClass = item.paused === true ? 'queued'
: item.status === 'done' ? 'done'
: item.status === 'running' ? 'running'
: item.status === 'failed' ? 'failed'
: 'queued'
Expand Down
35 changes: 35 additions & 0 deletions plugins/workflow/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3009,7 +3009,7 @@
// The hint's text is read when the drag is refused, so the host translation is put in place
// after the drawer mounted: a stub standing in for the whole host at mount time is read by the
// page's own reads (avatars, tool links) and hides the workflow list.
globalThis.__flybot__ = { ...(previous ?? {}), t: (key) => (key === 'Tasks' ? '任务' : key) }

Check warning on line 3012 in plugins/workflow/test/client.test.js

View workflow job for this annotation

GitHub Actions / plugins (workflow)

unicorn(no-useless-fallback-in-spread)

Empty fallbacks in spreads are unnecessary
try {
nodeAt('r1').props.onPointerDown({ clientX: 40, clientY: 160, target: { dataset: {} } })
canvas().props.onPointerMove({ clientX: 96, clientY: 208 })
Expand Down Expand Up @@ -4684,3 +4684,38 @@
document.body = prevBody
}
})

// A run frozen by a Stop that could not release its Bot stays `status: 'running'` with
// `paused: true` (#1476), and nothing more is handed. The strip called that state by the
// action's name ("Pause"), and runHistoryRows dropped `paused` altogether, so the Runs tab
// drew the frozen run as "Running" — the one tab a reader checks to see what is going on.
test('a paused run is named Paused in the strip and in the Runs history', async () => {
const graph = delivery()
graph.activeCount = 1
graph.runs = [{ ...graph.run, id: 'run-p', paused: true, status: 'running' }]
graph.run = graph.runs[0]
const call = async (name) => {
if (name === 'workflow/state') return { workflows: [graph], templates: [] }
return { ok: true, workflows: [graph] }
}
const view = mount(client.Section, { roster: { bots: [] }, call, actions: {} })
try {
await settle()
findAll(view.tree, node => node.type === 'button' && textOf(node).includes('发布交付流水线'))[0].props.onClick()
await settle()
const strip = findAll(view.tree, node => node.props?.['data-run-id'] === 'run-p')[0]
assert.ok(strip, 'the paused run has a strip')
const stripChips = findAll(strip, node => String(node.props?.className ?? '').includes('wf-chip')).map(textOf)
assert.deepEqual(stripChips, ['Paused'], 'the strip names the frozen run Paused, not by the Pause action')
assert.equal(findAll(view.tree, node => node.type === 'button' && textOf(node) === 'Resume').length, 1, 'a paused run still offers Resume')

button(view.tree, 'Progress').props.onClick()
await settle()
const rows = findAll(view.tree, node => String(node.props?.className ?? '').split(' ').includes('wf-hist'))
assert.equal(rows.length, 1, 'the Runs tab lists the paused run')
const chips = findAll(rows[0], node => String(node.props?.className ?? '').includes('wf-chip')).map(textOf)
assert.deepEqual(chips, ['Paused'], 'the Runs tab says Paused instead of Running')
} finally {
view.unmount()
}
})
Loading