feat(dashboard): refine activity telemetry and maps
CI / validate (push) Successful in 5m24s
Release / release (push) Successful in 11m6s

This commit is contained in:
dmg
2026-08-01 20:28:32 -04:00
parent 9116107917
commit ebc7c7df17
32 changed files with 695 additions and 72 deletions
+17 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { fillDailySeries } from "./admin-metrics";
import { fillDailySeries, mergeRiskActivity } from "./admin-metrics";
describe("admin dashboard metrics", () => {
it("fills missing UTC registration days with zero", () => {
@@ -13,4 +13,20 @@ describe("admin dashboard metrics", () => {
{ day: "2026-08-01", count: 1 },
]);
});
it("merges complete per-user VPN summaries with each user's latest observation", () => {
const latest = [
{ userId: "user-2", classification: "tor", observedAt: new Date("2026-08-01T11:00:00Z") },
{ userId: "user-1", classification: "proxy", observedAt: new Date("2026-08-01T12:00:00Z") },
];
const summaries = [
{ userId: "user-1", count: 2000, classifications: ["proxy", "vpn"], sources: ["game", "web"] },
{ userId: "user-2", count: 1, classifications: ["tor"], sources: ["web"] },
];
expect(mergeRiskActivity(latest, summaries)).toEqual([
expect.objectContaining({ userId: "user-1", count: 2000, classification: "proxy", classifications: ["proxy", "vpn"], sources: ["game", "web"] }),
expect.objectContaining({ userId: "user-2", count: 1, classification: "tor" }),
]);
});
});
+13
View File
@@ -3,6 +3,19 @@ export interface DailyCount {
count: number;
}
export function mergeRiskActivity<
T extends { userId: string; observedAt: Date },
S extends { userId: string | null },
>(latestRows: T[], summaryRows: S[]) {
const summaries = new Map(summaryRows.flatMap((summary) => summary.userId ? [[summary.userId, summary] as const] : []));
return latestRows
.flatMap((activity) => {
const summary = summaries.get(activity.userId);
return summary ? [{ ...activity, ...summary }] : [];
})
.sort((left, right) => right.observedAt.getTime() - left.observedAt.getTime());
}
export function fillDailySeries(rows: DailyCount[], end: Date, days: number) {
const counts = new Map(rows.map((row) => [row.day, Number(row.count)]));
const endDay = new Date(Date.UTC(end.getUTCFullYear(), end.getUTCMonth(), end.getUTCDate()));
+1
View File
@@ -10,6 +10,7 @@ describe("event filters", () => {
it("classifies events into operator-friendly views", () => {
expect(eventCategory("games.minecraft.account-manager.group.deleted")).toBe("groups");
expect(eventCategory("games.minecraft.account-manager.game.login.denied")).toBe("admission");
expect(eventCategory("games.minecraft.account-manager.game.player.connected")).toBe("admission");
expect(eventCategory("games.minecraft.account-manager.network.vpn-blocked")).toBe("security");
expect(eventCategory("games.minecraft.account-manager.auth.magic-link.consumed")).toBe("security");
expect(eventCategory("games.minecraft.account-manager.discord.nickname.updated")).toBe("identity");
+1 -1
View File
@@ -4,7 +4,7 @@ export type EventCategory = (typeof eventCategoryValues)[number];
export function eventCategory(type: string): Exclude<EventCategory, "all"> {
if (type.includes(".group.")) return "groups";
if (type.includes(".network.") || type.includes(".auth.") || type.includes("authentication") || type.includes("replay")) return "security";
if (type.includes(".game.login.")) return "admission";
if (type.includes(".game.")) return "admission";
if (type.includes(".discord.") || type.includes(".user.") || type.includes("minecraft-account")) return "identity";
return "operations";
}