openvidu/openvidu-dashboard/openspec/specs/analytics-ui/spec.md

18 KiB

analytics-ui Specification

Purpose

The Analytics page: usage of the deployment over the global time range as KPI tiles, time-series charts and breakdowns (ingress with its input type included), with locked cards for quality, diagnostics and top rooms, a locked per-node scope, and CSV export per section.

Requirements

Requirement: Analytics route and URL state

Edition: CE. /analytics SHALL be a range-scoped route whose state lives only in from, to, tz, granularity (default auto) and scope (default global); invalid values SHALL fall back silently, and scope SHALL be dropped when per-node-breakdown is not enabled.

  • GIVEN /analytics?from=now-7d&to=now&tz=UTC&granularity=1h
  • WHEN the page opens in a new browser
  • THEN every request carries the same range and granularity=1h, and the selectors show "Last 7 days", "UTC" and "1 h"

Scenario: Node scope in CE

  • GIVEN the UI loaded features: []
  • WHEN /analytics?scope=node:n1 opens
  • THEN the URL is rewritten without scope and every request uses the global scope

Requirement: Granularity selector

Edition: CE. The granularity selector SHALL offer auto, its label always naming what auto resolves to for the current range whatever value is selected, and only the explicit values the API accepts for the range; changing it SHALL write granularity to the URL. A chart of metrics stored only in hourly and daily documents SHALL be requested at 1h while the effective granularity is below 1h and read "Hourly" in its subtitle.

Scenario: Seven-day range

  • GIVEN from=now-7d&to=now&granularity=auto
  • WHEN the selector renders
  • THEN it reads "auto (1 h)" and offers 1h, 6h and 1d, not 5m or 15m

Scenario: Explicit value selected

  • GIVEN from=now-7d&to=now&granularity=1d
  • WHEN the user opens the selector
  • THEN "1 d" is selected and the auto option reads "auto (1 h)"

Scenario: Hourly-only chart on the default range

  • GIVEN the default 24 h range at auto
  • WHEN the Egress section renders
  • THEN the egressStarted chart is requested at 5m and the egressEnded chart at 1h with 24 buckets and the subtitle "Hourly"

Scenario: Old 24-hour range

  • GIVEN an absolute 24 h range that ended 30 days ago and a 5-minute rollup retention of 7 d
  • WHEN the selector renders
  • THEN it reads "auto (1 h)" and offers 1h, 6h and 1d

Scenario: Short 5-minute rollup retention

  • GIVEN GET /api/v1/info reports retention.statsMs of 7 d and retention.rollups5MinMs of 2 d, and an absolute 24 h range that ended 3 days ago
  • WHEN the selector renders
  • THEN it reads "auto (1 h)" and does not offer 5m or 15m

Scenario: Explicit values of the range

  • WHEN the selector lists its explicit values for the current range
  • THEN it offers 5m and 15m only for ranges of at most 24 h inside the 5-minute rollup retention, and no value above 1 000 buckets

Requirement: KPI tiles

Edition: CE. The tile row SHALL show, in this order, Rooms created, Participant sessions, Unique identities (labelled "sum of N UTC days"), Data up / down (up and down on two lines, each with its arrow), Connection success, Egress (count and total duration) and Ingress (sessions and total duration) from the summary endpoint with the shared formatters, show "n/a" with a hint for a value the summary omits and keep the last value while refreshing.

Scenario: Tiles from the summary

  • GIVEN a summary with roomsCreated: 1203, bytesUp: 1200000000000, bytesDown: 3400000000000, uniqueIdentities: { value: 4120, days: 7 }, ingressSessions: 12 and ingressDurationMs: 21600000
  • WHEN the tiles render
  • THEN they read "1,203", "↑ 1.2 TB" over "↓ 3.4 TB" under "Data up / down", "4,120" with the label "Unique identities (sum of 7 UTC days)", and the Ingress tile "12" with the hint "6 h 00 m in total"

Scenario: Tile order

  • WHEN the tile row renders
  • THEN the labels read Rooms created, Participant sessions, Unique identities, Data up / down, Connection success, Egress and Ingress

Scenario: Missing average

  • GIVEN a summary without avgRoomSize
  • WHEN the Rooms section tiles render
  • THEN the Average room size tile reads "n/a" with the hint "Not provided by this server version"

Requirement: Rooms section

Edition: CE. The Rooms section SHALL show a bar chart of roomsCreated and roomsEnded per bucket and the tiles Average room size (rounded to a whole number of participants) and Average room duration (in the shared duration format) from the summary.

Scenario: Rooms over 24 hours

  • GIVEN the default range at auto and a summary with avgRoomSize: 4.4 and avgRoomDurationMs: 360000
  • WHEN the section renders
  • THEN the chart shows 288 five-minute buckets with two bar series in fixed colours and the tiles read "4 participants" and "6 m 00 s"

Requirement: Participants section

Edition: CE. The Participants section SHALL show participantsJoined and participantsAborted as lines, connectionSuccessRate as a percentage line in its own chart, a participant kind breakdown without SIP, CONNECTOR and BRIDGE in which every kind has a distinct colour, and uniqueIdentities as bars per UTC day only when the effective granularity is 1d, otherwise a hint that unique identities are available at daily granularity.

Scenario: Weekly range hides unique identities

  • GIVEN from=now-7d at auto
  • WHEN the section renders
  • THEN no uniqueIdentities series is requested and the card shows "Unique identities are available at daily granularity" with a "Switch to 1 d" action

Scenario: Daily override

  • GIVEN granularity=1d
  • WHEN the section renders
  • THEN the unique identities bars are shown with the note "per UTC day" under the chart title, clear of the legend

Scenario: Kind breakdown

  • GIVEN a breakdown with STANDARD: 900, EGRESS: 40, AGENT: 12, INGRESS: 3 and SIP: 1
  • WHEN the section renders
  • THEN the chart shows Standard, Egress, Agent and Ingress, each in a different palette colour, and no SIP entry

Requirement: Client breakdowns section

Edition: CE. The Client breakdowns section SHALL show SDK, OS, browser and connection type from the breakdowns endpoint as donuts with legend, direct labels, fixed colours per key, "Other" beyond eight entries and a table toggle listing key, count and percent, every SDK, OS and connection type key shown by its shared friendly label (browser names as stored) and every percent in the shared percent format ("62.8 %").

Scenario: Nine SDKs

  • GIVEN nine SDK keys in the range
  • WHEN the SDK donut renders
  • THEN eight slices plus "Other" are drawn and the table toggle lists all nine with counts and percentages

Scenario: Donut percent

  • GIVEN an SDK breakdown in which js holds 628 of 1 000 sessions
  • WHEN the SDK donut renders
  • THEN the direct label of its slice and the percent column of its table read "62.8 %"

Scenario: Friendly labels

  • GIVEN a connection type breakdown with a known key and a key the label map does not know
  • WHEN the donut and its table render
  • THEN the known key reads its friendly label and the other key reads as stored

Requirement: Bandwidth section

Edition: CE. The Bandwidth section, headed "Data transferred", SHALL show bytesUp and bytesDown per bucket in the area chart "Data up / down", one unstacked area per direction with byte formatting, and the two totals from the summary as tiles.

Scenario: Bandwidth chart

  • GIVEN series with bytes in half of the buckets
  • WHEN the chart renders
  • THEN the x-axis spans the whole range, empty buckets read 0 and the tooltip formats values as "1.2 GB"

Scenario: Unstacked directions

  • GIVEN a bucket with 2 GB up and 3 GB down
  • WHEN the chart titled "Data up / down" renders
  • THEN the Down area reaches 3 GB from zero, not 5 GB on top of the Up area

Requirement: Egress section

Edition: CE. The Egress section SHALL show the tiles Total egress and Total duration (the summary's egressStarted and egressDurationMs), egressStarted as the bar chart "Egress started" beside the "Egress type" donut, and the egressEnded.<status> series as the stacked bar chart "Egress ended by status"; each chart requested at 1h while the effective granularity is finer reads the "Hourly" note under its title.

Scenario: Ended by status

  • GIVEN egresses ended as complete, aborted, limit_reached and failed (the egressEnded.<status> series)
  • WHEN the chart renders
  • THEN four stacked segments per bucket are drawn in four distinguishable colours, failed in the red slot and aborted apart from the red of aborted participants, and the legend names all four

Scenario: Totals

  • GIVEN a summary with egressStarted: 120 and egressDurationMs: 36000000
  • WHEN the section renders
  • THEN the tiles read Total egress "120" and Total duration "10 h 00 m"

Scenario: Status colours

  • WHEN the "Egress ended by status" chart renders
  • THEN failed statuses are drawn in red and every status in its own distinguishable colour

Requirement: PRO strip and locked cards

Edition: CE. Right after the Ingress section, in the same column of cards as the CE sections, the page SHALL render the cards Quality, Diagnostics and Top rooms; when the matching feature is disabled each card SHALL render the compact in-card lock, sized to its message rather than to the real card, without any request.

Scenario: Strip in CE

  • GIVEN the UI loaded features: []
  • WHEN the Analytics page renders
  • THEN the three PRO cards follow the Ingress section and show the compact lock with their feature title, each only as tall as its message, and no request to /analytics/top, /analytics/scopes or a PRO metric is sent

Requirement: Diagnostics section

Edition: CE. Without analytics-diagnostics the Diagnostics card SHALL render its lock, reading "Diagnostics is available in OpenVidu PRO".

Scenario: Locked in CE

  • GIVEN the UI loaded features: []
  • WHEN the card renders
  • THEN its heading reads "Diagnostics" and the lock reads "Diagnostics is available in OpenVidu PRO"

Requirement: Ingress section

Edition: CE. The Ingress section SHALL render right after the Egress section and before the Quality card, never locked, with the tiles Total sessions (ingressSessions of the summary) and Total duration (ingressDurationMs of the summary), ingressSessions as the bar chart "Ingress sessions", which reads "Hourly" in its note while it is requested at 1h below an effective 1h, and the ingressInputType breakdown as the donut "Ingress input type".

Scenario: Ingress sessions

  • GIVEN a summary with ingressSessions: 12 and ingressDurationMs: 21600000
  • WHEN the card renders
  • THEN bars per bucket are drawn and the tiles read Total sessions "12" and Total duration "6 h 00 m"

Scenario: Ingress input type donut

  • GIVEN a breakdown ingressInputType with rtmp 8, whip 3 and url 1
  • WHEN the card renders
  • THEN the donut "Ingress input type" shows the slices RTMP 8 (66.7 %), WHIP 3 (25.0 %) and URL 1 (8.3 %)

Scenario: Ingress in CE

  • GIVEN the UI loaded features: []
  • WHEN the Analytics page renders
  • THEN the Ingress section follows the Egress section and precedes the locked Quality card, shows its data without a lock, and its ingress series request is sent

Requirement: Per-node scope selector

Edition: CE. Without per-node-breakdown the page header SHALL show the scope selector disabled on "All nodes" with a lock icon whose tooltip says per-node analytics is available in OpenVidu PRO.

Scenario: Locked in CE

  • GIVEN features without per-node-breakdown
  • WHEN the header renders
  • THEN the selector reads "All nodes", is disabled with the lock icon and no /analytics/scopes request is sent

Requirement: CSV export per section

Edition: CE. Every section card except Top rooms SHALL have an "Export CSV" action in its header that downloads the CSV of the card's series or breakdowns with the current URL parameters through the shared HTTP service, saves it under the server file name and is disabled on locked cards.

Scenario: Bandwidth export

  • GIVEN the Bandwidth card over the last 7 days
  • WHEN the user clicks "Export CSV"
  • THEN GET /api/v1/analytics/export.csv?kind=series&metrics=bytesUp,bytesDown&from=…&to=…&tz=…&granularity=… is requested with the bearer token and the browser saves analytics-series-….csv

Scenario: Export failure

  • WHEN the export request fails
  • THEN a toast shows the error message and request id and the card stays interactive

Requirement: Card states

Edition: CE. Each card SHALL show a skeleton of its height only until its first data, "No data in this range" with a "Widen range" action, only as tall as that message, when every point is empty, and, when its request fails, the shared error notice with retry and request id above the card's last content, without affecting other cards; the page SHALL show an info banner when from is older than the retention of the source collection.

Scenario: One card fails

  • GIVEN the breakdowns request fails and the series requests succeed
  • WHEN the page renders
  • THEN the Client breakdowns card shows the error notice with "Retry" and the other cards show their data

Scenario: Failed refresh keeps the content

  • GIVEN every card showing data
  • WHEN a manual refresh fails for the series of the Rooms card
  • THEN the Rooms card keeps its chart and tiles under the error notice, and no skeleton replaces them

Scenario: Empty card

  • GIVEN a range in which the Egress card has no data
  • WHEN the page renders
  • THEN the Egress card shows "No data in this range" with "Widen range", only as tall as that message and not as tall as its charts would be

Scenario: Range older than the hourly retention

  • GIVEN from 500 days ago and an hourly retention of 400 d
  • WHEN the page renders
  • THEN a banner reads "Hourly data older than 400 days is not retained; daily values are shown" and the charts use 1d

Requirement: Analytics refresh

Edition: CE. The page SHALL NOT refresh on its own, whatever the range: the header's refresh control SHALL show only the manual refresh button and the last update time, without an interval menu, and a manual refresh SHALL re-query every section with one request per endpoint, keep the previous values while it runs and update the charts in place.

Scenario: Absolute range

  • GIVEN from=2026-10-01T00:00:00Z&to=2026-10-02T00:00:00Z
  • WHEN the page is open for several refresh intervals
  • THEN no further analytics requests are sent after the initial load

Scenario: Relative range

  • GIVEN from=now-24h&to=now
  • WHEN the page is open for several minutes
  • THEN no further analytics requests are sent after the initial load, and the header shows the refresh button and "updated hh:mm:ss" with no interval menu

Scenario: Manual refresh

  • GIVEN the page loaded with from=now-24h&to=now
  • WHEN the user presses the refresh button
  • THEN each analytics endpoint the page uses is requested once, the cards keep their values until the answers arrive, and the charts update without a skeleton

Requirement: Analytics chart notes

Edition: CE. Every time-series chart of the Analytics page SHALL name, in the note under its heading, its bucket ("Hourly" for a chart requested at 1h while the effective granularity is finer, otherwise the effective granularity) and the time zone of its time axis; the unique identities chart names UTC and keeps "per UTC day".

Scenario: Default range

  • GIVEN the default 24 h range at auto with tz=Europe/Madrid
  • WHEN the charts render
  • THEN the note of "Rooms created and ended" names the five-minute bucket and Europe/Madrid, and the note of "Egress ended by status" reads "Hourly" with Europe/Madrid

Scenario: Daily unique identities

  • GIVEN granularity=1d and tz=Europe/Madrid
  • WHEN the unique identities chart renders
  • THEN its note reads "per UTC day" and names UTC, not Europe/Madrid

Requirement: Media statistics coverage notes

Edition: CE. With mediaStats: "none" in the summary, the Data up / down tile SHALL read "—" with the hint "Not reported by the servers of this range" and the Bandwidth section SHALL show the notice "The servers of this range do not report media statistics" instead of its chart and tiles. With "partial", the tile and the section SHALL keep their values and add the note "Only from the servers that report media statistics".

Scenario: No server reports media statistics

  • GIVEN a summary with mediaStats: "none"
  • WHEN the page renders
  • THEN the Data up / down tile reads "—" with its hint, and the Bandwidth section shows the notice and no chart

Scenario: Some servers report media statistics

  • GIVEN a summary with mediaStats: "partial", bytesUp: 1200000000 and bytesDown: 3400000000
  • WHEN the page renders
  • THEN the tile reads "↑ 1.2 GB" over "↓ 3.4 GB" with the hint "Only from the servers that report media statistics", and the Bandwidth chart renders with the same note under its heading

Scenario: Every server reports media statistics

  • GIVEN a summary without mediaStats
  • WHEN the page renders
  • THEN the tile and the Bandwidth section render as before, without the note