Phase 1 for dashboard: skeleton api server and defining data types to be sent#439
Phase 1 for dashboard: skeleton api server and defining data types to be sent#439kushagra0902 wants to merge 10 commits intocameri:mainfrom
Conversation
…and ws connection management
…ole table sanner)
|
@kushagra0902 ci checks are failing, is this meant to be merged first/separately? |
There was a problem hiding this comment.
Pull request overview
Introduces a new “dashboard-service” Node/Express server intended to expose relay KPI data via HTTP (health + snapshot) and WebSocket streaming, with an initial polling/snapshot placeholder and early KPI collection scaffolding.
Changes:
- Added a standalone dashboard service (Express + WebSocketServer) with
/healthz,/api/v1/kpis/snapshot, and a WS stream endpoint. - Added placeholder polling/snapshot logic and an initial KPI collector service (DB queries for metrics).
- Added unit tests for the polling scheduler and a basic integration-style test for the dashboard service endpoints; updated
package.jsonscripts to run the service/tests.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| test/unit/dashboard-service/polling-scheduler.spec.ts | Unit tests for the new polling scheduler behavior. |
| test/unit/dashboard-service/app.spec.ts | Verifies health/snapshot/WS endpoints for the new dashboard service. |
| src/dashboard-service/ws/dashboard-ws-hub.ts | WebSocket hub: connection bootstrap + broadcast helpers. |
| src/dashboard-service/types.ts | Defines initial KPI snapshot and WS envelope types. |
| src/dashboard-service/services/snapshot-service.ts | Placeholder snapshot state + sequence refresh for phase-1 validation. |
| src/dashboard-service/services/kpi-collector-service.ts | KPI collection scaffolding with DB aggregate queries. |
| src/dashboard-service/polling/polling-scheduler.ts | Interval-based scheduler used to drive polling/broadcast. |
| src/dashboard-service/index.ts | Dashboard service entrypoint and shutdown wiring. |
| src/dashboard-service/handlers/request-handlers/get-kpi-snapshot-request-handler.ts | Request handler wiring for snapshot endpoint (controller adapter). |
| src/dashboard-service/handlers/request-handlers/get-health-request-handler.ts | Request handler wiring for health endpoint (controller adapter). |
| src/dashboard-service/controllers/get-kpi-snapshot-controller.ts | Controller returning the current KPI snapshot JSON payload. |
| src/dashboard-service/controllers/get-health-controller.ts | Controller returning JSON health response. |
| src/dashboard-service/config.ts | Env-based config parsing for host/port/wsPath/poll interval. |
| src/dashboard-service/app.ts | Core service composition: Express routes, WS server, polling loop. |
| src/dashboard-service/api/dashboard-router.ts | Router for dashboard API endpoints under /api/v1/kpis. |
| package.json | Adds dashboard dev/start scripts and dashboard-specific test scripts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { DashboardMetrics, EventsByKindCount, TopTalker } from '../types' | ||
| import { createLogger } from '../../factories/logger-factory' | ||
| import { DatabaseClient } from '../../@types/base' | ||
|
|
| import { createLogger } from '../factories/logger-factory' | ||
|
|
||
| import { createDashboardService } from './app' | ||
| import { getDashboardServiceConfig } from './config' | ||
|
|
||
| const debug = createLogger('dashboard-service:index') | ||
|
|
||
| const run = async () => { | ||
| const config = getDashboardServiceConfig() | ||
| console.info('dashboard-service: bootstrapping with config %o', config) | ||
| const service = createDashboardService(config) |
| await service.start() | ||
|
|
||
| const port = service.getHttpPort() | ||
|
|
||
| const healthResponse = await axios.get(`http://127.0.0.1:${port}/healthz`) | ||
| expect(healthResponse.status).to.equal(200) | ||
|
|
||
| const snapshotResponse = await axios.get(`http://127.0.0.1:${port}/api/v1/kpis/snapshot`) | ||
| expect(snapshotResponse.status).to.equal(200) | ||
|
|
||
| const snapshotJson = snapshotResponse.data as any | ||
| expect(snapshotJson.data).to.have.property('sequence') | ||
|
|
||
| const ws = new WebSocket(`ws://127.0.0.1:${port}/api/v1/kpis/stream`) | ||
|
|
||
| const connectedEvent = await new Promise<any>((resolve, reject) => { | ||
| const timeout = setTimeout(() => reject(new Error('timeout waiting for ws message')), 2000) | ||
| ws.once('message', (raw) => { | ||
| clearTimeout(timeout) | ||
| resolve(JSON.parse(raw.toString())) | ||
| }) | ||
| }) | ||
|
|
||
| expect(connectedEvent).to.have.property('type', 'dashboard.connected') | ||
|
|
||
| ws.close() | ||
| await service.stop() |
| private async getEventsByKind(): Promise<EventsByKindCount[]> { | ||
| const rows = await this.dbClient('events') | ||
| .select('event_kind') | ||
| .count('id as count') | ||
| .whereIn('event_kind', this.trackedKinds) | ||
| .groupBy('event_kind') | ||
| .orderBy('count', 'desc') as Array<{ event_kind: number, count: string }> | ||
|
|
||
| const other = await this.dbClient('events') | ||
| .whereNotIn('event_kind', this.trackedKinds) | ||
| .count<{ count: string }>('id as count') | ||
| .first() | ||
|
|
||
| const eventsByKind = rows.map((row) => { | ||
| return { | ||
| kind: String(row.event_kind), | ||
| count: toNumber(row.count), | ||
| } | ||
| }) | ||
|
|
||
| eventsByKind.push({ | ||
| kind: 'other', | ||
| count: toNumber(other?.count), | ||
| }) | ||
|
|
||
| return eventsByKind | ||
| } |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds an initial “dashboard-service” HTTP/WebSocket server intended to expose relay KPI snapshots/streams as a foundation for the dashboard requested in #144.
Changes:
- Introduces a new dashboard-service app with
/healthz,/api/v1/kpis/snapshot, and a WS stream endpoint. - Adds placeholder snapshot generation + polling scheduler to drive periodic updates.
- Adds initial unit tests and npm scripts to run the dashboard service and its tests.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| test/unit/dashboard-service/polling-scheduler.spec.ts | Unit tests for interval start/stop behavior. |
| test/unit/dashboard-service/app.spec.ts | E2E-ish unit test hitting health/snapshot and WS connect message. |
| src/dashboard-service/ws/dashboard-ws-hub.ts | WebSocket hub for connection bootstrap + broadcasting tick/snapshot. |
| src/dashboard-service/types.ts | Defines snapshot and WS envelope/message types. |
| src/dashboard-service/services/snapshot-service.ts | Placeholder snapshot state and refresh logic. |
| src/dashboard-service/services/kpi-collector-service.ts | Adds DB-backed KPI collection queries (not yet wired to snapshot). |
| src/dashboard-service/polling/polling-scheduler.ts | Interval-based scheduler used to refresh/broadcast snapshots. |
| src/dashboard-service/index.ts | Dashboard service entrypoint and signal/error handlers. |
| src/dashboard-service/handlers/request-handlers/get-kpi-snapshot-request-handler.ts | Wires snapshot controller to the shared request-handler wrapper. |
| src/dashboard-service/handlers/request-handlers/get-health-request-handler.ts | Health endpoint request handler. |
| src/dashboard-service/controllers/get-kpi-snapshot-controller.ts | Controller returning the current KPI snapshot. |
| src/dashboard-service/controllers/get-health-controller.ts | Controller returning {status:'ok'}. |
| src/dashboard-service/config.ts | Env-based configuration for host/port/wsPath/poll interval. |
| src/dashboard-service/app.ts | Creates express app + http server + ws server + polling loop. |
| src/dashboard-service/api/dashboard-router.ts | Router for KPI API endpoints. |
| package.json | Adds dev/start scripts and a dashboard-only unit test script. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| debug('starting scheduler with interval %d ms', this.intervalMs) | ||
|
|
||
| this.timer = setInterval(() => { | ||
| Promise.resolve(this.tick()) |
| import { DashboardMetrics, EventsByKindCount, TopTalker } from '../types' | ||
| import { createLogger } from '../../factories/logger-factory' | ||
| import { DatabaseClient } from '../../@types/base' | ||
|
|
| const debug = createLogger('dashboard-service:polling') | ||
|
|
||
| export class PollingScheduler { | ||
| private timer: NodeJS.Timer | undefined |
| this.timer = setInterval(() => { | ||
| Promise.resolve(this.tick()) | ||
| .catch((error) => { | ||
| console.error('dashboard-service: polling tick failed', error) | ||
| }) | ||
| }, this.intervalMs) |
| this.webSocketServer.clients.forEach((client) => { | ||
| client.close() | ||
| }) | ||
| this.webSocketServer.removeAllListeners() |
|
|
||
| await service.start() | ||
|
|
||
| const port = service.getHttpPort() | ||
|
|
||
| const healthResponse = await axios.get(`http://127.0.0.1:${port}/healthz`) | ||
| expect(healthResponse.status).to.equal(200) | ||
|
|
||
| const snapshotResponse = await axios.get(`http://127.0.0.1:${port}/api/v1/kpis/snapshot`) | ||
| expect(snapshotResponse.status).to.equal(200) | ||
|
|
||
| const snapshotJson = snapshotResponse.data as any | ||
| expect(snapshotJson.data).to.have.property('sequence') | ||
|
|
||
| const ws = new WebSocket(`ws://127.0.0.1:${port}/api/v1/kpis/stream`) | ||
|
|
||
| const connectedEvent = await new Promise<any>((resolve, reject) => { | ||
| const timeout = setTimeout(() => reject(new Error('timeout waiting for ws message')), 2000) | ||
| ws.once('message', (raw) => { | ||
| clearTimeout(timeout) | ||
| resolve(JSON.parse(raw.toString())) | ||
| }) | ||
| }) | ||
|
|
||
| expect(connectedEvent).to.have.property('type', 'dashboard.connected') | ||
|
|
||
| ws.close() | ||
| await service.stop() |
| "clean": "rimraf ./{dist,.nyc_output,.test-reports,.coverage}", | ||
| "build": "tsc --project tsconfig.build.json", | ||
| "prestart": "npm run build", | ||
| "start": "cd dist && node src/index.js", |
| export interface KPISnapshot { | ||
| sequence: number | ||
| generatedAt: string | ||
| status: 'placeholder' | ||
| metrics: { | ||
| eventsByKind: Array<{ kind: string, count: number }> | ||
| admittedUsers: number | null | ||
| satsPaid: number | null | ||
| topTalkers: TopTalker[] | ||
| } |
| const toNumber = (value: unknown): number => { | ||
| if (typeof value === 'number') { | ||
| return value | ||
| } | ||
|
|
||
| if (typeof value === 'string' && value !== '') { | ||
| return Number(value) | ||
| } | ||
|
|
||
| return 0 | ||
| } |
| import { createLogger } from '../factories/logger-factory' | ||
|
|
||
| import { createDashboardService } from './app' | ||
| import { getDashboardServiceConfig } from './config' | ||
|
|
||
| const debug = createLogger('dashboard-service:index') | ||
|
|
||
| const run = async () => { | ||
| const config = getDashboardServiceConfig() | ||
| console.info('dashboard-service: bootstrapping with config %o', config) | ||
| const service = createDashboardService(config) | ||
|
|
Description
Phase 1 for making a dashboard to show key KPI's related to relays. In this phase, data types for metrics sharing, endpoints for them making server for KPIs and tests for measuring these steps are added.
Related Issue
This is related to issue #144
Motivation and Context
The main motivation is discussed in the issue thread. TLDR: Making a dashboard so that users can keep track of relay performances against different configs.
How Has This Been Tested?
Unit tests that are used are added. Along with them manual testing using a local spun server and hitting end points have been used.
Screenshots (if appropriate):
N/A
Types of changes
Checklist: