mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 06:02:01 +00:00
定时任务前端复核修正(第 2 轮):错过已跳过的悬停不再替服务端认定是超出补发宽限,没有记原因时不指向不存在的原因
服务端把两种没有补发的触发都记成 kind=misfire、status=skipped_misfire:一是超出补发宽限,二是在宽限内恢复时 积压的那几次(只发起最近一次,更早的不补)。触发类型「定时」的悬停却一律写「错过后已超出补发宽限」,积压那条的 原因写的是「积压的触发不补,只发起最近一次」,同一行给出两个互相矛盾的原因,而这条明明是在宽限以内被跳过的。 - 触发类型的悬停改成不点明原因:「按计划应在此时触发,错过后没有补发,原因见本条记录」;本条没有记原因时去掉 「原因见本条记录」。 - 结局悬停的兜底说明(本条没有记原因时用)一起改:错过已跳过不再写「超出补发宽限」,两种原因都适用;发起失败 不再写「到点」(手动立即运行也会发起失败),也不再写「原因见本条记录」(用到兜底时本条恰好没有原因)。 - check-schedules 的触发历史夹具按服务端的原话各放一条积压不补、超出宽限的跳过,外加错过已跳过、发起失败各一条 没有记原因的;断言整行连同各处悬停只说本条记的原因,不说另一种,不指向不存在的原因。修复前这几项红 5 项。
This commit is contained in:
@@ -2792,19 +2792,26 @@ export const SCHEDULE_TEXT = {
|
||||
manual: '手动「立即运行」',
|
||||
misfire: '错过触发时间后,在补发宽限内恢复时补发的一次',
|
||||
} as Record<import('../types').ScheduleFireKind, string>,
|
||||
/** 错过且超出补发宽限、没有补发的(服务端记 kind=misfire、status=skipped_misfire):类型按「定时」写 */
|
||||
fireKindMissedHint: '按计划应在此时触发,错过后已超出补发宽限,没有补发',
|
||||
/**
|
||||
* 错过、没有补发的(服务端记 kind=misfire、status=skipped_misfire):类型按「定时」写。没有补发的原因有两种(超出
|
||||
* 补发宽限;宽限内恢复时积压的触发不补,只发起最近一次),这里不替服务端认定是哪一种,本条记了原因时指向它
|
||||
*/
|
||||
fireKindMissedHint: (hasReason: boolean) => `按计划应在此时触发,错过后没有补发${hasReason ? ',原因见本条记录' : ''}`,
|
||||
fireStatus: {
|
||||
started: '已发起',
|
||||
skipped_overlap: '上次未结束已跳过',
|
||||
skipped_misfire: '错过已跳过',
|
||||
failed_to_start: '发起失败',
|
||||
} as Record<import('../types').ScheduleFireStatus, string>,
|
||||
/**
|
||||
* 结局的一般说明,本条没有记原因时悬停用:只说这种结局是什么,不指向不存在的原因。错过已跳过的两种原因都适用,
|
||||
* 发起失败也可能是手动立即运行,不写「到点」
|
||||
*/
|
||||
fireStatusHint: {
|
||||
started: '已发起运行',
|
||||
skipped_overlap: '上一次运行还没有结束,按重叠策略跳过了这一次',
|
||||
skipped_misfire: '服务停机等原因错过了触发时间,且超出补发宽限,这一次没有发起',
|
||||
failed_to_start: '到点发起时被拒绝,原因见本条记录',
|
||||
skipped_misfire: '服务停机等原因错过了触发时间,这一次没有发起',
|
||||
failed_to_start: '未能发起运行,服务端未提供原因',
|
||||
} as Record<import('../types').ScheduleFireStatus, string>,
|
||||
event: { finished: '运行结束', failed: '运行失败', awaiting_approval: '等待审批' } as Record<import('../types').ScheduleEvent, string>,
|
||||
noticeStatus: { ok: '已发送', failed: '发送失败' } as Record<'ok' | 'failed', string>,
|
||||
|
||||
@@ -111,14 +111,15 @@ export function FireStatusChip({ fire }: { fire: ScheduleFire }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发类型:定时 / 手动 / 补发。服务端把错过的触发都记成 kind=misfire,宽限内补发了的是 started,超出宽限没补发的
|
||||
* 是 skipped_misfire:后者没有「补发」这回事,按「定时」写,结局由旁边的「错过已跳过」说
|
||||
* 触发类型:定时 / 手动 / 补发。服务端把错过的触发都记成 kind=misfire:宽限内补发的那一次照常有结局,没有补发的
|
||||
* 是 skipped_misfire。后者没有「补发」这回事,按「定时」写,结局由旁边的「错过已跳过」说。没有补发有两种原因(超出
|
||||
* 补发宽限;宽限内恢复时积压的那几次不补),悬停不替服务端认定是哪一种,否则会和本条记的原因自相矛盾
|
||||
*/
|
||||
export function FireKindChip({ fire }: { fire: ScheduleFire }) {
|
||||
const missed = fire.kind === 'misfire' && fire.status === 'skipped_misfire'
|
||||
const kind = missed ? 'scheduled' : fire.kind
|
||||
return (
|
||||
<span className="chip shrink-0" title={missed ? T.fireKindMissedHint : T.fireKindHint[kind]} data-fire-kind-chip={fire.kind}>
|
||||
<span className="chip shrink-0" title={missed ? T.fireKindMissedHint(!!fire.reason) : T.fireKindHint[kind]} data-fire-kind-chip={fire.kind}>
|
||||
{T.fireKind[kind] ?? kind}
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// 记录页看不出哪些运行来自定时任务、390px 窄屏被表单撑出横向滚动。
|
||||
// 还有几处时序和口径:晚到的刷新把刚切换的开关盖回去、先开触发历史再开设置时带着旧版本撞假的 409、保存途中又改了
|
||||
// 别的字段时替换过的地址明文留在框里、服务端会拒的组合(未发布 + 正式运行、必填没填、已发布版本里没有的字段)
|
||||
// 照样放行、测试消息的结果把服务端的整句又套一层。
|
||||
// 照样放行、测试消息的结果把服务端的整句又套一层、错过已跳过的那条悬停另说一个原因(和本条记的原因对不上)。
|
||||
//
|
||||
// 浏览器的本机时区故意设成 America/Los_Angeles,和定时任务的时区(Asia/Shanghai、America/New_York)都不同:
|
||||
// 两者相同时「按定时任务时区写」和「按本机写」在页面上看不出区别,时间相关的断言一律比对按定时任务时区写出的原文。
|
||||
@@ -371,18 +371,28 @@ const S2 = {
|
||||
|
||||
/**
|
||||
* 触发历史:25 条,新的在前,按 20 条一页给。计划时间都是北京时间周一 09:00(往前数第 i+1 周)。
|
||||
* 几种类型、结局、通知结果各放一条;错过且超出宽限的照服务端的记法是 kind=misfire、status=skipped_misfire
|
||||
* 几种类型、结局、通知结果各放一条。错过、没有补发的,服务端一律记成 kind=misfire、status=skipped_misfire,
|
||||
* 原因却有两种,各放一条、原因照服务端的原话写:f03 是停机一周多、在宽限内恢复时积压的那一次(最近一次就是补发的
|
||||
* f02),f05 是超出补发宽限的。f06、f07 没有记原因(约定里 reason 可以是 null),看兜底的说明
|
||||
*/
|
||||
function makeFires() {
|
||||
const out = []
|
||||
const local = (at) => {
|
||||
const w = wall(Date.parse(at), 'Asia/Shanghai')
|
||||
return `${w.y}-${pad2(w.mo)}-${pad2(w.d)} ${pad2(w.h)}:${pad2(w.mi)}`
|
||||
}
|
||||
const missed = { kind: 'misfire', status: 'skipped_misfire', run_id: null, run_status: null }
|
||||
for (let i = 0; i < 25; i++) {
|
||||
const at = iso(nextMonday9(-(i + 1)))
|
||||
let f = fire(`f${String(i).padStart(2, '0')}`, { fire_at: at, created_at: at })
|
||||
if (i === 0) f = { ...f, kind: 'manual', notified: [{ event: 'finished', status: 'ok', at }, { event: 'failed', status: 'failed', at }] }
|
||||
if (i === 1) f = { ...f, status: 'skipped_overlap', run_id: null, run_status: null, reason: '上一次运行(#9f8e7d)尚未结束' }
|
||||
if (i === 2) f = { ...f, kind: 'misfire', run_status: 'failed' }
|
||||
if (i === 3) f = { ...f, kind: 'misfire', status: 'skipped_misfire', run_id: null, run_status: null, reason: '服务停机,错过触发时间 3 小时,超出补发宽限 1 小时' }
|
||||
if (i === 3) f = { ...f, ...missed, reason: `服务未运行,错过了 ${local(at)} 的触发;积压的触发不补,只发起最近一次` }
|
||||
if (i === 4) f = { ...f, status: 'failed_to_start', run_id: null, run_status: null, reason: '工作流「门店周报」还没有发布:正式运行需要已发布的版本' }
|
||||
if (i === 5) f = { ...f, ...missed, reason: `服务未运行,错过了 ${local(at)} 的触发,超出补发宽限(1 小时),没有发起` }
|
||||
if (i === 6) f = { ...f, ...missed, reason: null }
|
||||
if (i === 7) f = { ...f, status: 'failed_to_start', run_id: null, run_status: null, reason: null }
|
||||
out.push(f)
|
||||
}
|
||||
return out
|
||||
@@ -1270,11 +1280,28 @@ await section('详情:触发历史分页', async () => {
|
||||
check('上次未结束已跳过:写原因,没有运行链接', t1.includes('上次未结束已跳过') && t1.includes('#9f8e7d') && await row('f01').locator('a[href^="/runs/"]').count() === 0,
|
||||
t1.replace(/\s+/g, ' '))
|
||||
check('补发写「补发」,运行状态照实写', (await row('f02').innerText()).includes('补发') && (await row('f02').innerText()).includes('失败'))
|
||||
check('错过已跳过写原因', (await row('f03').innerText()).includes('错过已跳过') && (await row('f03').innerText()).includes('超出补发宽限'))
|
||||
const k3 = row('f03').locator('[data-fire-kind-chip]')
|
||||
const k3title = (await k3.getAttribute('title')) ?? ''
|
||||
check('错过且超出宽限、没有补发的:类型不写「补发」,悬停也不说补发了一次', (await k3.innerText()).trim() === '定时'
|
||||
&& !k3title.includes('补发的一次') && k3title.includes('没有补发'), `${await k3.innerText()} | ${k3title}`)
|
||||
// 一行里给人看的字:正文连同各处悬停。错过、没有补发的原因只能是本条记的那一种,页面不能另替它认定一种
|
||||
const said = async (id) => [await row(id).innerText(),
|
||||
...await row(id).locator('[title]').evaluateAll((els) => els.map((e) => e.getAttribute('title')))].join(' | ').replace(/\s+/g, ' ')
|
||||
for (const [id, label, own, other] of [['f03', '宽限内积压的', '积压的触发不补', '超出补发宽限'], ['f05', '超出宽限的', '超出补发宽限', '积压']]) {
|
||||
const k = row(id).locator('[data-fire-kind-chip]')
|
||||
const kt = (await k.getAttribute('title')) ?? ''
|
||||
const t = await row(id).innerText()
|
||||
check(`错过已跳过(${label}):写本条的原因`, t.includes('错过已跳过') && t.includes(own), t.replace(/\s+/g, ' '))
|
||||
check(`错过已跳过(${label}):类型写「定时」,悬停说没有补发、指向本条的原因,不说补发了一次`, (await k.innerText()).trim() === '定时'
|
||||
&& !kt.includes('补发的一次') && kt.includes('没有补发') && kt.includes('原因见本条记录'), `${await k.innerText()} | ${kt}`)
|
||||
const all = await said(id)
|
||||
check(`错过已跳过(${label}):整行连同悬停都不说另一种原因(${other})`, !all.includes(other), all)
|
||||
}
|
||||
const s6 = await said('f06')
|
||||
check('错过已跳过、没有记原因:不写原因行,悬停不替它认定原因、不指向不存在的原因', s6.includes('错过已跳过')
|
||||
&& !s6.includes('原因:') && !s6.includes('超出补发宽限') && !s6.includes('积压') && !s6.includes('原因见'), s6)
|
||||
const st6 = (await row('f06').locator('[data-fire-status-chip]').getAttribute('title')) ?? ''
|
||||
check('……结局的悬停写这种结局的一般说明', st6.includes('错过') && st6.includes('没有发起'), st6)
|
||||
const s7 = await said('f07')
|
||||
const st7 = (await row('f07').locator('[data-fire-status-chip]').getAttribute('title')) ?? ''
|
||||
check('发起失败、没有记原因:悬停说清没有原因,不指向不存在的原因', s7.includes('发起失败') && !s7.includes('原因:')
|
||||
&& !s7.includes('原因见') && st7.includes('未能发起运行') && st7.includes('未提供原因'), s7)
|
||||
check('发起失败写原因', (await row('f04').innerText()).includes('发起失败') && (await row('f04').innerText()).includes('还没有发布'))
|
||||
const when = row('f05').locator('time')
|
||||
check('计划时间悬停看时区', ((await when.getAttribute('title')) ?? '').includes('Asia/Shanghai'), (await when.getAttribute('title')) ?? '')
|
||||
|
||||
Reference in New Issue
Block a user