openvidu/openvidu-dashboard/openspec/specs/overview-api/spec.md

144 lines
8.1 KiB
Markdown

# overview-api Specification
## Purpose
The overview endpoint: one cheap, index-backed response with the live counters, today's totals, recent rooms and
stale hints that the Overview page and the live counter tiles poll.
## Requirements
### Requirement: Overview endpoint
Edition: CE. `GET /api/v1/overview` SHALL require a bearer token, accept only `tz` (IANA zone or `UTC`, default
`UTC`) and answer `200` with `generatedAt`, `live`, `today` and `recentRooms`; an unknown `tz` SHALL be rejected with
`400`. The writer health, schema and migration are served by `GET /api/v1/status`.
#### Scenario: Default response
- **GIVEN** a deployment with a running writer
- **WHEN** the client requests `GET /api/v1/overview` with a valid token
- **THEN** the response is `200` with `generatedAt` within one second of the server clock and the keys `live`, `today` and `recentRooms`, and no `nodes` key on CE
#### Scenario: Unknown timezone
- **WHEN** the client requests `GET /api/v1/overview?tz=Mars/Olympus`
- **THEN** the response is `400` with code `VALIDATION_ERROR` and `details.issues[0].path` equal to `["tz"]`
### Requirement: Live counters
Edition: CE. `live` SHALL carry `rooms` (status `active`), `participants` (status `active`, kind `STANDARD`),
`services` (status `active`, every kind but `STANDARD`), `participantsByKind` (every kind, `0` when absent),
`publishers` (active STANDARD with `isPublisher`), `egresses` (status `starting`, `active` or `ending`) and
`ingressSessions` (status `publishing`).
#### Scenario: Mixed kinds
- **GIVEN** 12 active rooms holding 85 active STANDARD, 2 SIP, 3 EGRESS and 2 AGENT participants, 54 STANDARD publishers and 1 INGRESS publisher, 3 egresses in status `active`, 1 in `ending` and 1 `complete`, and 1 `publishing` ingress session
- **WHEN** the client requests the overview
- **THEN** `live` is `{ rooms: 12, participants: 85, services: 7, participantsByKind: { STANDARD: 85, SIP: 2, INGRESS: 0, EGRESS: 3, AGENT: 2, CONNECTOR: 0, BRIDGE: 0 }, publishers: 54, egresses: 4, ingressSessions: 1, stale: … }`
#### Scenario: Joining participants are not active
- **GIVEN** a room with one `active` and one `joining` STANDARD participant
- **WHEN** the client requests the overview
- **THEN** `live.participants` is `1`
### Requirement: Stale hints
Edition: CE. `live.stale` SHALL carry `rooms` and `participants`, the number of `active` documents whose `lastStatsAt`
is older than `olderThanMs` at `generatedAt`, and `olderThanMs` itself: the stale window `max(90 s, 60 s + I + 15 s)` of
the batch interval I that `GET /api/v1/status` reports as `writer.batchIntervalMs` (the largest among the nodes seen in
the last hour, 10 s when none reports one); documents without `lastStatsAt` SHALL NOT be counted.
#### Scenario: Stale room and participants
- **GIVEN** nodes reporting `batchIntervalMs: 10000`, an active room whose `lastStatsAt` is 100 s old with two active participants with `lastStatsAt` 100 s old, and another active room with fresh stats
- **WHEN** the client requests the overview
- **THEN** `live.stale` is `{ rooms: 1, participants: 2, olderThanMs: 90000 }`
#### Scenario: Signal-only participant
- **GIVEN** an active participant that never published nor subscribed and has no `lastStatsAt`
- **WHEN** the client requests the overview
- **THEN** it is not counted in `live.stale.participants`
#### Scenario: Slow writer
- **GIVEN** a node seen in the last hour with `batchIntervalMs: 60000` and an active room with one active participant, both with `lastStatsAt` 100 s old
- **WHEN** the client requests the overview
- **THEN** `live.stale` is `{ rooms: 0, participants: 0, olderThanMs: 135000 }`
#### Scenario: Nodes without a reported interval
- **GIVEN** node documents without `batchIntervalMs`
- **WHEN** the client requests the overview
- **THEN** `live.stale.olderThanMs` is `90000`
#### Scenario: Window at the default interval
- **GIVEN** the default batch interval of 10 s
- **WHEN** the overview is requested
- **THEN** `live.stale.olderThanMs` is 90 000
### Requirement: Today's figures
Edition: CE. `today` SHALL carry `tz`, `since` (local midnight of `tz` in UTC), `rooms`, `participantSessions`,
`participantMinutes`, `egresses`, `ingressSessions` and `approximate`, summed from the `global` hourly rollups
whose bucket starts at or after the hour containing `since`; `approximate` is `true` when `since` is not on an
hour boundary.
#### Scenario: Whole-hour zone
- **GIVEN** `tz=Europe/Madrid` at 12:40 local time on 2026-10-13 and hourly `global` rollups since 22:00 UTC of the previous day summing `rooms.created` 143, `participants.joined` 1204, `participants.minutes` 18340, `egress.started` 21 and `ingress.sessionsStarted` 4
- **WHEN** the client requests the overview
- **THEN** `today` is `{ tz: "Europe/Madrid", since: "2026-10-12T22:00:00.000Z", rooms: 143, participantSessions: 1204, participantMinutes: 18340, egresses: 21, ingressSessions: 4, approximate: false }`
#### Scenario: Half-hour zone
- **GIVEN** `tz=Asia/Kolkata`
- **WHEN** the client requests the overview
- **THEN** `today.since` is 18:30 UTC of the previous day, the sum starts at the 18:00 UTC bucket and `approximate` is `true`
#### Scenario: No rollups yet
- **GIVEN** an empty `analytics_hourly` collection
- **WHEN** the client requests the overview
- **THEN** every counter of `today` is `0` and `approximate` is `false`
### Requirement: Recent rooms
Edition: CE. `recentRooms` SHALL list at most 10 rooms of any status ordered by `createdAt` descending, each with
`sid`, `name`, `status`, `createdAt`, `endedAt?`, `durationMs?`, `closedBy?`, `participants { active, sessions,
unique }` (`unique`: distinct STANDARD identities), `nodeId?`, `lastStatsAt?` and `stale?` (`true` for an active room
whose `lastStatsAt` is older than the stale window at `generatedAt`), never internal id arrays, `schemaVersion` or
`expiresAt`.
#### Scenario: Twelve rooms
- **GIVEN** 12 rooms, the newest two active
- **WHEN** the client requests the overview
- **THEN** `recentRooms` has 10 items starting with the two active rooms, each with the listed fields and no `participantSids`
#### Scenario: Unique identities of a recent room
- **GIVEN** a closed room with 7 STANDARD sessions of 4 identities and one EGRESS session
- **WHEN** the client requests the overview
- **THEN** its `recentRooms` item has `participants: { active: 0, sessions: 7, unique: 4 }`
#### Scenario: Stale recent room
- **GIVEN** an active room whose `lastStatsAt` is 2 minutes before `generatedAt` and a writer batch interval of 10 s
- **WHEN** the client requests the overview
- **THEN** its `recentRooms` item has `stale: true` and an active room with stats 20 s old has no `stale` field
#### Scenario: Empty database
- **WHEN** no room exists
- **THEN** `recentRooms` is `[]`
### Requirement: Conditional overview
Edition: CE. The response SHALL carry a weak `ETag` derived from the body without `generatedAt` and
`Cache-Control: private, no-cache`; a request whose `If-None-Match` matches SHALL answer `304` without a body.
#### Scenario: Quiet deployment
- **GIVEN** a client holding the `ETag` of the last overview
- **WHEN** it polls again 10 s later and no counter, room, rollup or node fact changed
- **THEN** the response is `304` with the same `ETag` and an empty body
#### Scenario: Writer batch without changes on CE
- **GIVEN** the same client on CE
- **WHEN** a batch updated only `nodes.lastSeenAt`
- **THEN** the response is `304`
### Requirement: Index-backed overview
Edition: CE. Every overview query SHALL use an index of the shared contract (`rooms { status, createdAt }`,
`participants { status, kind }`, `egresses { status, startedAt }`, `ingress_sessions { status }`,
`analytics_hourly { scope, bucket }`), examining at most each collection's active documents plus 20 rooms; `nodes`,
bounded by the node count, is read whole for the stale window's batch interval; `schema_meta` is not read.
#### Scenario: Explain plans
- **GIVEN** a database generated by the data generator at scenario M
- **WHEN** each overview query is explained
- **THEN** no plan on `rooms`, `participants`, `egresses`, `ingress_sessions` or `analytics_hourly` contains `COLLSCAN`, and `totalDocsExamined` of the room and participant aggregations equals the number of active documents