Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest';
import { TYPE_PALETTE } from '../lib/colorAllocators/typeColors.ts';
import { NODE_PALETTE } from '../lib/nodeColors.ts';
import { TABLE_PALETTE } from '../lib/tableColors.ts';

describe('categorical palettes', () => {
it('node, table, and type palettes are pairwise disjoint', () => {
const palettes = { NODE_PALETTE, TABLE_PALETTE, TYPE_PALETTE };
const seen = new Map<string, string>();
for (const [name, palette] of Object.entries(palettes)) {
for (const color of palette) {
const key = color.toLowerCase();
expect(seen.get(key), `${color} appears in both ${seen.get(key)} and ${name}`).toBeUndefined();
seen.set(key, name);
}
}
});
});
44 changes: 1 addition & 43 deletions src/features/instance/status/analytics/__tests__/time.test.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,5 @@
import { describe, expect, it } from 'vitest';
import {
formatAxisTick,
formatTooltipTime,
getTimeRangeFromPreset,
getTimezoneAbbr,
TIME_PRESETS,
} from '../lib/time.ts';

describe('TIME_PRESETS', () => {
it('has all required presets', () => {
const labels = TIME_PRESETS.map((p) => p.label);
expect(labels.includes('15m')).toBeTruthy();
expect(labels.includes('30m')).toBeTruthy();
expect(labels.includes('1h')).toBeTruthy();
expect(labels.includes('6h')).toBeTruthy();
expect(labels.includes('12h')).toBeTruthy();
expect(labels.includes('1d')).toBeTruthy();
expect(labels.includes('3d')).toBeTruthy();
expect(labels.includes('1w')).toBeTruthy();
expect(labels.includes('1mo')).toBeTruthy();
});
});

describe('getTimeRangeFromPreset', () => {
it('returns a range where start < end', () => {
const now = Date.now();
const range = getTimeRangeFromPreset('1h', now);
expect(range.startTime < range.endTime).toBeTruthy();
expect(range.endTime).toBe(now);
});

it('returns correct duration for 1h preset', () => {
const now = Date.now();
const range = getTimeRangeFromPreset('1h', now);
expect(range.endTime - range.startTime).toBe(60 * 60 * 1000);
});

it('returns correct duration for 1d preset', () => {
const now = Date.now();
const range = getTimeRangeFromPreset('1d', now);
expect(range.endTime - range.startTime).toBe(24 * 60 * 60 * 1000);
});
});
import { formatAxisTick, formatTooltipTime, getTimezoneAbbr } from '../lib/time.ts';

describe('formatAxisTick', () => {
it('formats a timestamp to local time string', () => {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** Protocol / type series colors. Six hues spread across the color wheel for
* accessibility and colorblind-friendliness. Disjoint from NODE_PALETTE and
* TABLE_PALETTE (enforced in test/colorAllocators/disjoint.test.ts). */
* TABLE_PALETTE (enforced in __tests__/paletteDisjointness.test.ts). */
export const TYPE_PALETTE: readonly string[] = [
'#0d9488', // teal-600
'#dc2626', // red-600
Expand Down
29 changes: 0 additions & 29 deletions src/features/instance/status/analytics/lib/time.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,3 @@
import type { PresetOption, TimeRange } from '../types/analytics.ts';

export const TIME_PRESETS: PresetOption[] = [
{ label: '15m', value: '15m', durationMs: 15 * 60 * 1000 },
{ label: '30m', value: '30m', durationMs: 30 * 60 * 1000 },
{ label: '1h', value: '1h', durationMs: 60 * 60 * 1000 },
{ label: '6h', value: '6h', durationMs: 6 * 60 * 60 * 1000 },
{ label: '12h', value: '12h', durationMs: 12 * 60 * 60 * 1000 },
{ label: '1d', value: '1d', durationMs: 24 * 60 * 60 * 1000 },
{ label: '3d', value: '3d', durationMs: 3 * 24 * 60 * 60 * 1000 },
{ label: '1w', value: '1w', durationMs: 7 * 24 * 60 * 60 * 1000 },
{ label: '1mo', value: '1mo', durationMs: 30 * 24 * 60 * 60 * 1000 },
];

export function getTimeRangeFromPreset(preset: string, now: number = Date.now()): TimeRange {
const found = TIME_PRESETS.find((p) => p.value === preset);
if (!found) { throw new Error(`Unknown preset: ${preset}`); }
return { startTime: now - found.durationMs, endTime: now };
}

const axisFormatter = new Intl.DateTimeFormat(undefined, {
hour: 'numeric',
minute: '2-digit',
Expand Down Expand Up @@ -71,12 +51,3 @@ export function formatBytes(bytes: number): string {
const value = bytes / k ** i;
return `${value.toFixed(value < 10 ? 1 : 0)} ${units[i]}`;
}

export function formatBytesPerMin(bytes: number): string {
if (bytes === 0) { return '0 B/min'; }
const units = ['B/min', 'KB/min', 'MB/min', 'GB/min', 'TB/min'];
const k = 1000;
const i = Math.floor(Math.log(bytes) / Math.log(k));
const value = bytes / k ** i;
return `${value.toFixed(value < 10 ? 1 : 0)} ${units[i]}`;
}
37 changes: 3 additions & 34 deletions src/features/instance/status/analytics/pipeline/index.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,6 @@
// Spec registry barrel. Step 1 populates this file with per-metric spec imports
// and a `specRegistry` map. Step 0 ships the scaffold + known-names export so the
// allowlist codegen can read something.

// When a spec lands at src/lib/metricSpecs/<kebab>.ts it is added to KNOWN_METRICS
// here. This list is the source of truth for the backend allowlist generator.
export const KNOWN_METRICS = [
'replication-latency',
'resource-usage',
'memory',
'mqtt-connections',
'ws-connections',
'main-thread-utilization',
'bytes-sent',
'bytes-received',
'table-size',
'duration',
'success',
'transfer',
'tls-reused',
'connection',
'cpu-usage',
'db-read',
'db-write',
'db-message',
'response_200',
'utilization',
'database-size',
'storage-volume',
'cache-hit',
'cache-resolution',
] as const;

export type KnownMetric = (typeof KNOWN_METRICS)[number];
// Spec registry barrel. Each metric spec lives in this directory
// (pipeline/<kebab>.tsx, or .ts for renderer-less specs) and is registered in
// `specRegistry` below — the registry keys are the canonical metric names.

import type { SpecRegistryEntry } from '../types/analytics.ts';
import { BytesReceivedRenderer, bytesReceivedSpec } from './bytes-received.tsx';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function FallbackRenderer({ metric, records, theme, hint }: Props) {

const kebab = metric.replace(/_/g, '-');
const banner = isDev
? `Unspecced metric "${metric}" — add a spec at src/lib/metricSpecs/${kebab}.ts for a tailored view.`
? `Unspecced metric "${metric}" — add a spec at src/features/instance/status/analytics/pipeline/${kebab}.tsx for a tailored view.`
: null;

return (
Expand Down Expand Up @@ -117,7 +117,7 @@ export function FallbackRenderer({ metric, records, theme, hint }: Props) {
<SmallMultiples panels={panels} theme={theme} />
{overflow > 0 && (
<div style={{ fontSize: 11, marginTop: 4, opacity: 0.7 }}>
{`… and ${overflow} more fields not shown. Add a spec at src/lib/metricSpecs/${kebab}.ts to customize.`}
{`… and ${overflow} more fields not shown. Add a spec at src/features/instance/status/analytics/pipeline/${kebab}.tsx to customize.`}
</div>
)}
</div>
Expand Down
Loading