Skip to content

Phase 1 for dashboard: skeleton api server and defining data types to be sent#439

Open
kushagra0902 wants to merge 10 commits intocameri:mainfrom
kushagra0902:main
Open

Phase 1 for dashboard: skeleton api server and defining data types to be sent#439
kushagra0902 wants to merge 10 commits intocameri:mainfrom
kushagra0902:main

Conversation

@kushagra0902
Copy link
Copy Markdown
Contributor

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

  • Non-functional change (docs, style, minor refactor)
  • Bug fix (non-breaking change which fixes an issue)
  • [x ] New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • [ x] My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • [ x] I have read the CONTRIBUTING document.
  • [x ] I have added tests to cover my code changes.
  • [ x] All new and existing tests passed.

@cameri
Copy link
Copy Markdown
Owner

cameri commented Apr 18, 2026

@kushagra0902 ci checks are failing, is this meant to be merged first/separately?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.json scripts 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.

Comment on lines +1 to +4
import { DashboardMetrics, EventsByKindCount, TopTalker } from '../types'
import { createLogger } from '../../factories/logger-factory'
import { DatabaseClient } from '../../@types/base'

Comment thread package.json Outdated
Comment thread src/dashboard-service/polling/polling-scheduler.ts
Comment on lines +1 to +11
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)
Comment on lines +15 to +41
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()
Comment on lines +57 to +83
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>
Copilot AI review requested due to automatic review settings April 19, 2026 04:43
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())
Comment on lines +1 to +4
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
Comment on lines +22 to +27
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()
Comment on lines +14 to +41

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()
Comment thread package.json
"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",
Comment on lines +6 to +15
export interface KPISnapshot {
sequence: number
generatedAt: string
status: 'placeholder'
metrics: {
eventsByKind: Array<{ kind: string, count: number }>
admittedUsers: number | null
satsPaid: number | null
topTalkers: TopTalker[]
}
Comment on lines +9 to +19
const toNumber = (value: unknown): number => {
if (typeof value === 'number') {
return value
}

if (typeof value === 'string' && value !== '') {
return Number(value)
}

return 0
}
Comment on lines +1 to +12
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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants