新增一个统计模块
最常见的一类定制:加一个新的分析。按这一页从 SQL 模板走到页面和文档,一个模块就同时出现在管理界面、/api/v1、Agent 工具和看板里。
下文以一个假想的模块「按小时分布」(键 hourly,看一天 24 个小时里各有多少页面浏览)为例,文件路径都相对仓库根。
改动一览
| 步骤 | 文件 | 必须 |
|---|---|---|
| 1. SQL 模板 | evs-aggr/internal/stat/sql/traffic.sql | 是 |
| 2. 查询函数 | evs-aggr/internal/stat/mod_traffic.go | 是 |
| 3. 注册 | evs-aggr/internal/stat/registry.go | 是 |
| 4. 契约 | evs-aggr/api/openapi.yaml,然后 go generate ./... | 进 OpenAPI 时 |
| 5. 测试 | evs-aggr/internal/apitest/、internal/stat/modules_integration_test.go、evs-test/openapi/cases.py | 是 |
| 6. 界面 | evs-dash/src/modules.ts、views/stat/、locales/ | 有页面时 |
| 7. 文档 | evs-docs/src/zh/guide/stat.md 等、更新日志 | 是 |
1. 写 SQL 模板
模板放在 internal/stat/sql/ 下按分组的文件里,用 Go 模板的 {{define "名字"}} 定义,参数一律是 ClickHouse 的绑定参数:
sql
{{define "hourly"}}
SELECT toHour(toDateTime(event_time, {tz:String})) AS hour, count() AS pv, uniqExact(distinct_id) AS uv
FROM evs_event
WHERE project_id = {project_id:UUID} AND event_date BETWEEN {from:Date} AND {to:Date}
AND event_type = 'track' AND event = '$pageview'{{.PF}}
GROUP BY hour
ORDER BY hour WITH FILL FROM 0 TO 24
{{end}}- 必须带
project_id = {project_id:UUID}和event_date范围,sql_static_test.go会挡住不带的模板。 {{.PF}}是属性筛选拼出来的条件,支持筛选的模块都要留着它。- 没有筛选时能用聚合表(
evs_agg_event_daily、evs_agg_dim_daily)就用聚合表,查得更快;参考_partials.sql里的kpi_body、rank_body。 - 口径要和 指标口径 一致:UV 按
distinct_id去重,会话以 30 分钟为界,只数$pageview。
2. 写查询函数
每个模块有两个函数:Run 返回给页面和接口的数据,Table 返回同一条 SQL(导出 CSV 和 Agent 工具用它)。
go
func runHourly(ctx context.Context, q *Query) (any, error) {
params := q.Base()
rows, err := q.rows(ctx, "hourly", q.data(params), params)
if err != nil {
return nil, err
}
return map[string]any{"rows": rows}, nil
}
func tableHourly(ctx context.Context, q *Query) (string, ch.Params, error) {
params := q.Base()
sql, err := Render("hourly", q.data(params))
return sql, params, err
}q.Base() 带好了项目、时区、日期参数;q.data(params) 处理属性筛选。返回的空结果要是空数组,不要返回 404。
3. 在注册表里加一行
internal/stat/registry.go 的 Modules 是唯一的注册表,一行决定这个模块出现在哪里:
go
{Key: "hourly", Name: "按小时分布", NameEn: "By hour", Path: "hourly", OpenAPI: true, Agent: true, Board: true, CSV: true,
Chart: "bar", MaxSpan: 90, NeedsRange: true, Run: runHourly, Table: tableHourly,
Desc: "Pageviews and visitors by hour of day in the project time zone"},| 字段 | 作用 |
|---|---|
Key | 模块键:路由、接口路径、Agent 工具名、看板卡片类型都用它,定了不要改 |
Path | /dash/stats/<Path> 与 /api/v1/projects/{projectId}/<Path>;页面路由默认是 /stat/<Path> |
OpenAPI · Agent · Board · CSV | 是否进开放接口、是否作为 Agent 工具、能否加入看板、能否导出 |
Chart | 前端用哪种图 |
MaxSpan · HourSpan | 最长日期跨度(天);小时粒度的最长跨度 |
Params | 额外参数:名字、类型(enum、int、string、events、bool01)、默认值、范围。同一份定义校验三个出口的参数,也生成 Agent 工具的参数说明 |
Heavy · Realtime · Compare | 30 秒超时;较短的缓存时间;支持对比上一段 |
Desc | 英文一句话说明,给契约和 Agent 工具用 |
4. 更新契约
进 OpenAPI 的模块要在 api/openapi.yaml 里补上路径(照着相邻的模块写:参数、返回结构、最长跨度),然后在 evs-aggr 里重新生成:
bash
cd evs-aggr
go generate ./... # 生成 api/openapi.json、api/modules.json、api/config-keys.json
go test ./internal/contracts/...契约测试会检查:生成文件是否最新、契约里的路径与路由表是否一致、契约里没有管理接口。
5. 写测试
- 接口用例:在
internal/apitest/里加一个测试,用夹具数据断言数字;bothFor会同时调用/dash/stats和/api/v1,要求两边返回一样。期望的数字写进internal/testkit/expect.json,不要在测试里另写一套。 - 集成测试:
internal/stat/modules_integration_test.go会在真实 ClickHouse 上把全部模块跑一遍,新模块自动包含在内。 - 登记用例:在
evs-test/openapi/cases.py里登记编号和 Go 测试函数名。
bash
cd evs-test
uv run main.py --case unit # 静态检查、注册表、契约
uv run main.py --case T-Q30 # 你新加的接口用例6. 加页面
在 evs-dash 里:
src/modules.ts加上同样的一行(键、path、路由、出口标记、图、最长跨度、参数与缺省值),yarn test:registry与api/modules.json对照。- 路由由
modules.ts自动生成;需要专门的页面时在src/views/stat/写一个,并在src/router/routes.ts里挂上。通用的排行、表格页可以复用现有组件。 src/views/stat/moduleViews.ts补表格列与空态说明。src/locales/zh-CN.ts与en.ts补nav.hourly等文案(两份的键必须一致);侧栏分组在src/components/shell/nav.ts。yarn run check全绿(lint、样式、类型、单元测试、构建、包体积)。
详见 仪表盘开发。
7. 写文档
- 在官网 看数 对应的分组表格里加一行:这个菜单回答什么问题。
- 新指标要在 指标口径 里写清算法;新的上限写进 限制与上限。
- API 参考由
openapi.json渲染:在evs-test里执行uv run main.py --case T-D09 --fix把新的契约同步进官网。 - 在 更新日志 当月的文件里记一句变更摘要。
- 需要截图时在
evs-test/media/capture.py的截图清单里加一项,重新生成(见 官网与文档)。
检查清单
- SQL 带项目与日期范围、参数都是绑定参数,
go test ./internal/stat/...通过。 go generate ./...之后契约测试通过,modules.json与modules.ts一致。- 管理界面和
/api/v1返回相同的数字,接口用例通过。 - 页面有空态、中英文文案齐全,
yarn run check通过。 - 官网对应页面、指标口径、更新日志已经更新。