运行对比页复查修正:互换时不重取运行列表,拆分句的总变化为 0 时写「0」,展开控件只指向在场的明细

- 互换、换了本次之后,新的运行记录还在路上时沿用上一次知道的工作流:下拉不再先退回「正在读取」、再把同一工作流
  的运行列表取一遍(能比的两次运行本来就是同一个工作流);check-compare 补这一项
- 口径变了、数值却碰巧没变(两效应正好抵消)时服务端不给变化量的写法,拆分那句话的总变化写「0」,不再写成
  「两者之和等于总变化 无变化」
- 指标展开、查看 SQL 的按钮只在明细展开时才带 aria-controls,不指向页面上不存在的元素;参数前后两个值之间的箭头
  对读屏补一句「改为」
This commit is contained in:
ThearchyHelios
2026-10-05 09:06:18 +08:00
parent 72da2e178a
commit a0837cc82c
5 changed files with 28 additions and 6 deletions
+2
View File
@@ -2878,6 +2878,8 @@ export const RUN_COMPARE_TEXT = {
rowsBase: (base: string) => `基准 ${base}`,
rowsTarget: (target: string) => `本次 ${target}`,
literals: '变化的参数',
/** 参数前后两个值之间的箭头,读屏念这个 */
changedTo: '改为',
baseSql: '基准的 SQL',
targetSql: '本次的 SQL',
bothSql: '两次相同的 SQL',
+6 -2
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { Link, useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { GitCompareArrows } from 'lucide-react'
import { EmptyState, PageHeader, Skeleton, Spinner } from '../../components/ui'
@@ -44,7 +44,11 @@ export function ComparePage() {
const targetRun = useRunRecord(target)
const baseRun = useRunRecord(base)
const workflowId = targetRun.data?.workflow_id
// 换基准的下拉按本次的工作流取。互换、换了本次之后新的运行记录还在路上时沿用上一次知道的工作流:能比的两次运行
// 本来就是同一个工作流,不必让下拉先退回「正在读取」、再把同一份列表取一遍
const lastWorkflow = useRef<string | null | undefined>(undefined)
if (targetRun.state === 'ok') lastWorkflow.current = targetRun.data?.workflow_id ?? null
const workflowId = targetRun.state === 'ok' ? targetRun.data?.workflow_id : lastWorkflow.current
const candidates = useCandidates(workflowId)
const cmp = useComparison(target, base)
+12 -3
View File
@@ -108,7 +108,7 @@ function MetricRows({ m, k, detailId, open, onToggle }: {
<Fragment>
<tr className={clsx('border-b align-top last:border-0', open && 'bg-hover')} data-compare-metric={k} data-class={m.classification}>
<th scope="row" className={clsx(STICKY, open ? 'bg-hover' : 'bg-panel', 'px-2.5 py-2 text-left font-normal')}>
<button type="button" data-compare-expand="" aria-expanded={open} aria-controls={detailId}
<button type="button" data-compare-expand="" aria-expanded={open} aria-controls={open ? detailId : undefined}
title={open ? T.collapse(m.name) : T.expand(m.name)} onClick={onToggle}
className="group flex max-w-[220px] items-start gap-1 rounded-sm text-left focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[color:var(--accent)]">
<ChevronRight size={12} aria-hidden
@@ -128,7 +128,7 @@ function MetricRows({ m, k, detailId, open, onToggle }: {
<td className="px-2.5 py-2"><ClassChip value={m.classification} /></td>
<td className="px-2.5 py-2">
{m.decomposition ? (
<Split d={m.decomposition} total={deltaText(m)} />
<Split d={m.decomposition} total={totalText(m)} />
) : m.decomposition_note ? (
<p className="max-w-[280px] text-2xs leading-relaxed text-dim [overflow-wrap:anywhere]" data-compare-decomp-note="">
{m.decomposition_note}
@@ -155,6 +155,15 @@ function deltaText(m: RunCompareMetric): string {
return m.delta === 0 ? T.noChange : NONE
}
/**
* 拆分那句话里的总变化:口径变了、数值却碰巧没变时服务端不给变化量的写法(两效应正好抵消),写「0」,
* 不写成「两者之和等于总变化 无变化」
*/
function totalText(m: RunCompareMetric): string {
if (m.delta_rendered) return m.delta_rendered
return m.delta === 0 ? '0' : NONE
}
/**
* 能严格拆分的:口径效应、数据效应并列,下面一段两截的条形按两者的绝对值分长短。一正一负(互相抵消)时负的那截画
* 斜纹:长短只说谁的分量大,方向看数的正负号
@@ -205,7 +214,7 @@ function MetricDetail({ m }: { m: RunCompareMetric }) {
)}
{m.decomposition && (
<p className="text-xs leading-relaxed text-fg" data-compare-decomp-sum="">
{T.splitSum(m.decomposition.caliber_rendered, m.decomposition.data_rendered, deltaText(m))}
{T.splitSum(m.decomposition.caliber_rendered, m.decomposition.data_rendered, totalText(m))}
</p>
)}
<Inputs m={m} />
+3 -1
View File
@@ -59,7 +59,7 @@ function QueryItem({ q, k, nth }: { q: RunCompareQuery; k: string; nth: number |
<span className="tnum text-2xs text-faint" data-compare-rows="">{rows}</span>
{foldable && (
<button type="button" className="btn btn-xs btn-ghost text-faint" data-compare-sql-toggle=""
aria-expanded={showSql} aria-controls={sqlId} onClick={() => setShowSql((v) => !v)}>
aria-expanded={showSql} aria-controls={showSql ? sqlId : undefined} onClick={() => setShowSql((v) => !v)}>
<ChevronRight size={11} aria-hidden className={clsx('transition-transform', showSql && 'rotate-90')} />
{showSql ? T.hideSql : T.showSql}
</button>
@@ -82,7 +82,9 @@ function QueryItem({ q, k, nth }: { q: RunCompareQuery; k: string; nth: number |
{q.literals.map((l, i) => (
<li key={i} className="flex min-w-0 flex-wrap items-center gap-1.5">
<code className="mono rounded px-1 [overflow-wrap:anywhere]" style={MARK.del}>{valueText(l.base)}</code>
{/* 箭头只给眼睛看,读屏念「改为」 */}
<span className="text-faint" aria-hidden>→</span>
<span className="sr-only">{T.changedTo}</span>
<code className="mono rounded px-1 [overflow-wrap:anywhere]" style={MARK.add}>{valueText(l.target)}</code>
</li>
))}
+5
View File
@@ -558,6 +558,8 @@ await section('换基准、互换', async () => {
check('换基准:重新请求对比', compareHits(log, T, B2) === 1)
check('换基准:页面换成新的一份', (await texts(page, '[data-compare-notes] li')).includes(NOTE_B2))
check('换基准:基准卡跟着换', (await text(page, '[data-compare-run="base"]')).includes('9/21'))
const lists = () => log.filter((k) => k.startsWith('GET /api/runs?') && k.includes(`workflow_id=${WF}`)).length
const listsBefore = lists()
await page.locator('button[data-compare-swap]').click()
await until(async () => new URL(page.url()).searchParams.get('target') === B2)
await until(async () => compareHits(log, B2, T) === 1)
@@ -566,6 +568,9 @@ await section('换基准、互换', async () => {
check('互换:重新请求对比', compareHits(log, B2, T) === 1)
await settled(page)
check('互换:两张卡跟着对调', (await page.locator('[data-compare-run="target"] a[data-compare-run-link]').getAttribute('href')) === `/runs/${B2}`)
check('互换:同一工作流的运行列表不重取,下拉照常可用', lists() === listsBefore
&& await page.locator('select[data-compare-base-picker]').isEnabled() && await page.locator('select[data-compare-base-picker]').inputValue() === T,
`${listsBefore} → ${lists()}`)
})
await section('过时的答复不盖掉新的', async () => {