feat: 前端指标体系增强 — Dashboard/Metrics API、2×4 统计区、趋势图面积+异常标记、连续状态列
- 新增 GET /api/dashboard 合并原 summary+targets 首屏接口 - 新增 GET /api/targets/:id/metrics 合并原 stats+trend 概览接口 - 后端指标纯函数:可用率、百分位、故障段分析、连续状态、UTC 小时分桶 - ProbeStore 窗口取数方法替代全量历史查询 - SummaryCards 扩展为 4 卡片(新增异常事件数)+ 数据新鲜度展示 - 表格新增「连续」列(Tag 渲染 capped 状态) - OverviewTab 重构为 2×4 Statistic 多维度布局 - TrendChart 改为延迟范围面积图 + 红色异常标记点 - 删除旧路由(summary/targets/trend)和 computeTrendStats - 同步 delta specs 到主 specs 并归档变更
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import type { TrendPoint } from "../../../src/shared/api";
|
||||
|
||||
import { computeTrendStats } from "../../../src/web/utils/stats";
|
||||
|
||||
describe("computeTrendStats", () => {
|
||||
test("空趋势返回 0 统计", () => {
|
||||
expect(computeTrendStats([])).toEqual({ downChecks: 0, totalChecks: 0, upChecks: 0 });
|
||||
});
|
||||
|
||||
test("汇总总检查、正常和异常数量", () => {
|
||||
const points: TrendPoint[] = [
|
||||
{ availability: 80, avgDurationMs: 100, hour: "2025-01-01T00:00:00.000Z", totalChecks: 10 },
|
||||
{ availability: 40, avgDurationMs: 200, hour: "2025-01-01T01:00:00.000Z", totalChecks: 5 },
|
||||
];
|
||||
|
||||
expect(computeTrendStats(points)).toEqual({ downChecks: 5, totalChecks: 15, upChecks: 10 });
|
||||
});
|
||||
|
||||
test("按每个趋势点四舍五入正常数量", () => {
|
||||
const points: TrendPoint[] = [
|
||||
{ availability: 33.3, avgDurationMs: null, hour: "2025-01-01T00:00:00.000Z", totalChecks: 3 },
|
||||
{ availability: 66.7, avgDurationMs: null, hour: "2025-01-01T01:00:00.000Z", totalChecks: 3 },
|
||||
];
|
||||
|
||||
expect(computeTrendStats(points)).toEqual({ downChecks: 3, totalChecks: 6, upChecks: 3 });
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { subtractHours } from "../../../src/web/utils/time";
|
||||
import { formatDurationUnit, formatRelativeTime, isOlderThan, subtractHours } from "../../../src/web/utils/time";
|
||||
|
||||
describe("subtractHours", () => {
|
||||
test("正常扣减小时", () => {
|
||||
@@ -27,3 +27,38 @@ describe("subtractHours", () => {
|
||||
expect(result.toISOString()).toBe("2025-01-15T12:00:00.000Z");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatRelativeTime", () => {
|
||||
const now = new Date("2025-01-01T00:02:00.000Z");
|
||||
|
||||
test("格式化秒和分钟", () => {
|
||||
expect(formatRelativeTime("2025-01-01T00:01:45.000Z", now)).toBe("15秒前");
|
||||
expect(formatRelativeTime("2025-01-01T00:00:00.000Z", now)).toBe("2分钟前");
|
||||
});
|
||||
|
||||
test("无时间返回占位", () => {
|
||||
expect(formatRelativeTime(null, now)).toBe("尚无检查数据");
|
||||
expect(formatRelativeTime("invalid", now)).toBe("尚无检查数据");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatDurationUnit", () => {
|
||||
test("按秒、分钟、小时动态格式化", () => {
|
||||
expect(formatDurationUnit(1500)).toEqual({ suffix: "秒", value: 1.5 });
|
||||
expect(formatDurationUnit(120000)).toEqual({ suffix: "分钟", value: 2 });
|
||||
expect(formatDurationUnit(5400000)).toEqual({ suffix: "小时", value: 1.5 });
|
||||
});
|
||||
|
||||
test("空时长返回占位", () => {
|
||||
expect(formatDurationUnit(null)).toEqual({ suffix: "", value: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("isOlderThan", () => {
|
||||
test("判断时间是否超过阈值", () => {
|
||||
const now = new Date("2025-01-01T00:02:00.000Z");
|
||||
|
||||
expect(isOlderThan("2025-01-01T00:00:59.000Z", 60000, now)).toBe(true);
|
||||
expect(isOlderThan("2025-01-01T00:01:30.000Z", 60000, now)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user