build_campaign_timeline flattens a campaign's child Runs into offset-sorted
per-Run entries (distinct from the report's 12-bucket aggregation), reusing a
shared _run_window_offset口径 so both views place a run identically. Exposes
GET /campaigns/{id}/timeline and the api.ts type/call. (v0.6 ticket 06)
87 lines
9.7 KiB
Markdown
87 lines
9.7 KiB
Markdown
# v0.6 Spec(UX 升级)— 评估活动页 UX 升级
|
||
|
||
**状态**: ready-for-agent
|
||
**领域词汇**: 见 CONTEXT.md「周期评估」章节(评估活动 / 服务周期窗口 / 活动计划 / 可用性)
|
||
**关键决策**: ADR-0002(通过率含执行失败)、ADR-0003(活动分期);本批不改判定/调度/报告聚合语义
|
||
**批次说明**: 本 spec 是 v0.6 评估活动功能线的 **UX 升级批次**(①倍速反向输入 ②计划时间轴预览 ④展开行时间轴),与 `spec.md`(v1 静态地基)并存。线③「活动分析 Agent」属新功能,另立 **v0.7** spec。
|
||
**工单**: 本 spec 派生 v0.6/issues/06–10。
|
||
|
||
## Problem Statement
|
||
|
||
评估活动(Campaign)的能力在 v0.6 静态地基已落地,但**新增/查看活动的页面对用户不友好**:
|
||
|
||
1. **时间倍速是裸数字**:新增活动时「时间倍速」是一个 `InputNumber`(默认 1,仅一句 tooltip),用户得自己心算「填多大倍速能让 24h 窗口几分钟跑完」。用户真正关心的是「加速后多久跑完」,而不是抽象的倍速值。
|
||
2. **活动计划不直观**:计划是一组 `场景 + 偏移小时 + 次数` 的表单行,看不出「在这个窗口的哪些时段、跑哪个场景、跑几次」的时间分布全貌。
|
||
3. **看不到活动过程**:活动列表每行不可展开;跑完只能进报告抽屉看聚合曲线,无法直观回看「整个活动过程中,各个子 Run 在时间上如何依次发生、各自成败」。
|
||
|
||
## Solution
|
||
|
||
对活动页做三项 UX 升级,**不触碰**判定、调度、报告聚合的任何语义:
|
||
|
||
- **① 倍速反向输入**:新增活动时用户填「窗口长度」+「希望加速后多久跑完」,系统自动派生并只读展示时间倍速;正式线用「实时」开关(×1)。用户从此按「耗时」思考,而非「倍速」。
|
||
- **② 计划时间轴预览**:保留(优化后的)行编辑器做精确录入,其上方增加一条**只读时间轴预览**,把计划条目按偏移画在代表窗口的水平轴上、颜色区分场景、标注次数,随表单实时刷新。
|
||
- **④ 展开行时间轴**:活动列表每行可展开为一条**横向窗口时间轴**,把该活动的子 Run 按「加速后窗口偏移」定位成节点、颜色表状态、hover 显详情、点击跳该 Run 报告;进行中活动随现有轮询实时长出新节点。②与④共用同一套时间轴视觉语言。
|
||
|
||
## User Stories
|
||
|
||
1. 作为质量负责人,我想在新增活动时直接填「希望这个 24 小时窗口在多久内跑完」(如 1 小时),系统替我算好倍速,以便我不必心算倍速。
|
||
2. 作为质量负责人,我想在填写时实时看到「加速后约 X 跑完」,以便确认压缩节奏符合预期。
|
||
3. 作为质量负责人,我想在正式线一键选「实时」(真实墙钟、倍速 ×1),以便采到真实的凌晨/高峰表现。
|
||
4. 作为质量负责人,当我把目标耗时填得比窗口还长(意味着倍速 <1、比真实还慢)时,我想被拦下或纠正,以便不产生无意义的减速活动。
|
||
5. 作为质量负责人,我想在编辑活动计划时看到一条时间轴预览,把每个计划条目按其窗口偏移画出来,以便一眼看清「哪些时段跑哪个场景、跑几次」的分布。
|
||
6. 作为质量负责人,我想计划预览用颜色区分不同场景、并标注每个条目的次数,以便区分密集与稀疏时段。
|
||
7. 作为质量负责人,我想计划预览随我编辑表单实时更新,以便边填边看。
|
||
8. 作为质量负责人,我想在活动列表里展开任意一行,看到这个活动的完整过程时间轴,以便回看整个服务周期里发生了什么。
|
||
9. 作为质量负责人,我想过程时间轴把每个子 Run 按其「加速后窗口偏移」定位成节点,以便看清子 Run 在窗口内的时间分布。
|
||
10. 作为质量负责人,我想每个节点用颜色表示状态(完成/失败/运行中/取消),以便一眼识别问题时段。
|
||
11. 作为质量负责人,我想 hover 节点看到场景、通过率、时延,以便无需展开报告即可速览。
|
||
12. 作为质量负责人,我想点击节点直接跳到该子 Run 的报告,以便下钻排查。
|
||
13. 作为质量负责人,当我展开一个进行中的活动时,我想时间轴随轮询长出新派生的子 Run,以便实时观察活动推进。
|
||
14. 作为质量负责人,我想活动列表/详情处也用「加速后约 X 完成」而非裸倍速展示,以便和新增表单的心智一致。
|
||
|
||
## Implementation Decisions
|
||
|
||
### ① 倍速反向输入(前端为主,API 契约不变)
|
||
- 新增活动表单把「时间倍速」输入替换为两项:**窗口长度**(沿用预设下拉 6/12/24/48/72h)+ **目标加速耗时**(数值 + 单位,默认分钟)。
|
||
- 前端派生 `time_scale = window_seconds / target_real_seconds`,作为**只读**展示(如「倍速 ×24 · 加速后约 1 小时跑完」),提交时仍在 payload 里发 `time_scale`。**后端 `POST /campaigns` 契约与 `Campaign.time_scale` 语义完全不变**。
|
||
- 「实时」开关:选中即 `target_real_seconds = window_seconds` → `time_scale = 1.0`,并禁用目标耗时输入。
|
||
- 约束:目标耗时必须 ≤ 窗口长度(即 `time_scale ≥ 1`);违反时表单校验拦截(对应 US-4)。
|
||
- 派生与反算(由 scale+window 反推「加速后耗时」用于列表/详情展示)抽成**纯前端 util**(如 `campaignTime.ts` 的 `deriveTimeScale` / `acceleratedDuration`),集中口径、便于复用。
|
||
|
||
### ② 计划时间轴预览(纯前端)
|
||
- 新增一个**只读时间轴预览组件**,输入 = 表单当前的 `window_seconds` + `plan` 条目 + 场景名映射,输出 = 一条水平窗口轴,条目按 `offset_seconds` 定位,颜色按 `scenario_id` 稳定分配,标注 `count`。
|
||
- 不涉及任何后端改动;数据完全来自表单本地状态,随编辑实时重渲染。
|
||
- 与④的过程时间轴共享底层展示(见下「共享组件」)。
|
||
|
||
### ④ 展开行时间轴(新后端纯接缝 + 前端展开行)
|
||
- **新后端纯函数接缝** `build_campaign_timeline(campaign, runs) -> list[CampaignTimelineEntry]`(放 `evaluation/report.py` 或紧邻的模块),每条含:`run_id / scenario_id / scenario_name / offset_seconds(加速后窗口偏移,clamp 到 [0, window])/ status / pass_rate / avg_latency_ms / started_at`。
|
||
- 偏移计算复用现有报告口径:`offset = (run.started_at - campaign.started_at).total_seconds() * time_scale`,clamp 到 `[0, window_seconds]`;未开始的子 Run(无 started_at)与取消 Run 的处理在此接缝内明确定义。
|
||
- `pass_rate` 直接取子 Run `summary.pass_rate`(权威,只读不重算,沿 v0.5/v0.6 约定)。
|
||
- **新端点** `GET /campaigns/{id}/timeline` → `{entries: CampaignTimelineEntry[]}`,返回逐 Run 扁平数组(区别于报告的 12 桶聚合曲线)。
|
||
- **前端**:活动列表行改为 `expandable`,展开区渲染横向窗口时间轴:子 Run 节点按 `offset_seconds` 定位、颜色=状态、hover 显场景/通过率/时延、点击跳 `/reports?run={run_id}`。展开进行中活动时,随现有 5s 轮询刷新时间轴数据。
|
||
|
||
### 共享组件
|
||
- ②的计划预览与④的过程时间轴共用一个**底层横向时间轴展示组件**(窗口轴 + 定位标记 + 颜色 + hover),两处用不同数据适配:②喂「计划条目」,④喂「子 Run」。保证视觉语言一致,避免两套实现。
|
||
|
||
### API 契约(新增项)
|
||
- `GET /campaigns/{id}/timeline` → `{ "entries": [ { "run_id", "scenario_id", "scenario_name", "offset_seconds", "status", "pass_rate", "avg_latency_ms", "started_at" } ] }`。
|
||
- 其余活动端点、`Campaign` 模型、`time_scale` 语义均不变。
|
||
|
||
## Testing Decisions
|
||
|
||
- **好测试只测外部行为**:接缝喂构造好的输入、断言输出,不测内部实现细节。
|
||
- **主接缝:`build_campaign_timeline`(纯函数)** —— 唯一新增的可测后端接缝。单测覆盖:正常子 Run 的偏移计算(含 `time_scale` 压缩)、clamp 到窗口边界、未开始/取消/失败子 Run 的处理、字段映射(pass_rate 取自 summary、场景名映射)、空活动。**Prior art**:`tests/unit/test_report.py` / `test_campaign_report.py`(同样喂 campaign+runs 断言聚合 dict 的模式)。
|
||
- **端点集成测试**:`GET /campaigns/{id}/timeline` 的 200/404、字段形状。**Prior art**:`tests/integration/test_runs_api.py`、现有 campaigns router 测试。
|
||
- **前端无测试框架**:①倍速反向输入(派生/反算/实时开关/≤窗口校验)、②计划预览、④展开时间轴,均以 `npx tsc --noEmit` + `npm run build` + 浏览器实操验证(含进行中活动的轮询刷新)。`deriveTimeScale`/`acceleratedDuration` 写成纯函数,便于将来接入前端测试。
|
||
|
||
## Out of Scope
|
||
|
||
- **线③ 活动分析 Agent + 服务质量改善建议**:新功能,另立 **v0.7** spec(含新增「分析」模型岗位、全局默认分析模型 + 活动可覆盖、多步分析 Agent、结构化分析报告、按需生成+缓存)。
|
||
- **计划的拖拽编辑**:本批时间轴预览为**只读**;拖拽调偏移/点击空白新增等交互不做。
|
||
- **跨活动对比**、活动列表分页/筛选增强,均不在本批。
|
||
|
||
## Further Notes
|
||
|
||
- ②预览与④过程时间轴的共享底层组件是本批的关键复用点,也是唯一较有设计含量的前端接缝——先把它的输入接口设计干净(喂「带窗口偏移的标记数组」),两处适配即可。
|
||
- 列表/详情的「加速后约 X 完成」展示与新增表单的派生口径必须走同一个纯 util,避免两处算法漂移(呼应 v0.6 起「口径收敛到一处」的做法)。
|