mirror of https://github.com/OpenVidu/openvidu.git
openvidu-dashboard: archive ui-review-fixes, keep only the specs
parent
8c389a724c
commit
c9077f6f7a
|
|
@ -1,2 +0,0 @@
|
|||
schema: spec-driven
|
||||
created: 2026-11-03
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
- **Charts:** `charts/chart-options.ts` builds every time-series option.
|
||||
- The x-axis is an ECharts `time` axis whose labels are `HH:mm`, `MM-dd HH:mm` or `MM-dd` by span, with no minimum
|
||||
interval, so a 3-minute range ticks every 20–30 s.
|
||||
- The y-axis has no minimum interval either.
|
||||
- Step series use `step: 'end'`: each value is drawn from its bucket start to the next point, so the last point has no
|
||||
segment.
|
||||
- **Skeletons:** `ovd-skeleton` paints its bars at once and is removed on the first response, which flashes when the
|
||||
response takes tens of milliseconds.
|
||||
- **Durations:** `formatDuration()` truncates at every unit. `ovd-duration` formats both running and stored durations
|
||||
with it.
|
||||
- **About storage:** `InfoService.storageEntry()` lists each collection's indexes and dates it only when an index leads
|
||||
with `ts`, `bucket` or `createdAt`. The analytics indexes of the time fields are:
|
||||
- `rooms.createdAt`, `ingresses.createdAt`;
|
||||
- `participants { joinedAt: -1, _id: -1 }`;
|
||||
- `egresses { startedAt: -1, _id: -1 }` and `ingress_sessions { startedAt: -1, _id: -1 }`;
|
||||
- `events.ts`, `analytics_5min.bucket`, `analytics_hourly.bucket`;
|
||||
- `analytics_daily { scope, bucket }`;
|
||||
- the `track_stats` time series, indexed on its metadata before `ts`;
|
||||
- `nodes`, with no time index and one document per node;
|
||||
- `tracks` and `track_subscriptions`, with their time field only behind `participantSid`, `roomSid`, `subscriberSid`
|
||||
or `trackSid`.
|
||||
- **Global search:** `exactIdTarget()` recognises `TR_` through `entityIdKind()` but drops it, because no route takes
|
||||
a track ID.
|
||||
- **Browser and OS labels:** the rollups key browsers by family, and an empty browser falls into `other` until the
|
||||
writer change adds `none`. Pages that show a client's OS print the reported string (`linux` from the Go SDK, `Linux`
|
||||
from the JS SDK).
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- One rule per chart property, applied in `timeSeriesOption()` for every chart of every edition.
|
||||
- No new inputs on the chart, the skeleton or the duration components.
|
||||
|
||||
**Non-Goals:**
|
||||
- Changing the label formats of the axes or the retention notices.
|
||||
- New indexes for tracks and subscriptions.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1. The x-axis interval follows the label unit
|
||||
|
||||
`minInterval` is 60 000 ms when the labels show minutes (`HH:mm`, `MM-dd HH:mm`) and 86 400 000 ms when they show days.
|
||||
It comes from the same span rule as the label format.
|
||||
|
||||
Rejected: passing the bucket width from every chart. The labels are the constraint, and the granularity is already in
|
||||
the chart note.
|
||||
|
||||
### D2. Integer data gets integer ticks
|
||||
|
||||
`yAxis.minInterval` is 1 when every non-null value of every series is an integer.
|
||||
|
||||
Rejected: an `integer` input on the chart, which every count chart of every edition would have to set.
|
||||
|
||||
### D3. A step series is extended to `to`
|
||||
|
||||
When the last point of a step series is not null and starts before `to`, `timeSeriesOption()` appends `[to, value]`, so
|
||||
`step: 'end'` draws the last bucket up to the end of the range. A dense series always ends in the bucket that holds
|
||||
`to`.
|
||||
|
||||
Rejected: `step: 'start'`, which would draw every value one bucket early.
|
||||
|
||||
### D4. The skeleton fades in after 300 ms
|
||||
|
||||
Every loading placeholder keeps its height from the start and carries the global class `ovd-skeleton-reveal`, whose CSS
|
||||
animation keeps its opacity at 0 for 300 ms and then shows it: `ovd-skeleton`, the skeleton rows of `ovd-data-table`
|
||||
and the KPI tile skeleton. Pages keep their loading logic. Under `prefers-reduced-motion` the global rule shortens the
|
||||
fade and the delay stays.
|
||||
|
||||
Rejected:
|
||||
- delaying the loading flag in every loader, which touches every page and both loaders;
|
||||
- keeping the previous content, which does not exist on a first load.
|
||||
|
||||
### D5. Stored durations are rounded, running ones are not
|
||||
|
||||
`formatDuration(ms, { mode })` rounds to the smallest unit it will show by default:
|
||||
- it picks the unit from the magnitude, rounds, and formats again when rounding crosses a unit (59 600 ms → 60 s →
|
||||
"1 m 00 s");
|
||||
- `mode: 'floor'` keeps the current truncation;
|
||||
- `ovd-duration` passes `floor` while `running` is true, both for `liveDurationMs` and for `liveTotalMs`.
|
||||
|
||||
Rejected: rounding live durations, which would make a counter jump to the next second halfway through.
|
||||
|
||||
### D6. A fixed time field per collection
|
||||
|
||||
`InfoService` holds one map from collection to field, and optional filter, and reads `find(filter).sort({ field: 1 })
|
||||
.limit(1)` with a projection of the field. This replaces the index discovery and saves a `listIndexes` per collection.
|
||||
|
||||
| Collection | Field |
|
||||
| --- | --- |
|
||||
| `rooms`, `ingresses` | `createdAt` |
|
||||
| `participants` | `joinedAt` |
|
||||
| `egresses`, `ingress_sessions` | `startedAt` |
|
||||
| `events` | `ts` |
|
||||
| `analytics_5min`, `analytics_hourly` | `bucket` |
|
||||
| `analytics_daily` | `bucket` with `{ scope: 'global' }` |
|
||||
| `nodes` | `firstSeenAt` (one document per node, no index needed) |
|
||||
|
||||
`tracks`, `track_subscriptions` and `track_stats` have no entry, and About explains the "—". The `track_stats` query
|
||||
was explained on a generated dataset: the time series has no index on its time alone, so the plan is a bounded sort
|
||||
over a scan of the buckets.
|
||||
|
||||
Rejected:
|
||||
- new `{ publishedAt }` and `{ subscribedAt }` indexes, two more indexes on the most written collections for one About
|
||||
cell;
|
||||
- a sort without an index, a collection scan every 60 s.
|
||||
|
||||
### D7. Plain version text
|
||||
|
||||
`sidenav-footer.ts` renders the version in a `span`.
|
||||
|
||||
### D8. The room averages say why they are missing
|
||||
|
||||
`missingHint()` takes the reason: `roomsEnded === 0` gives "No room ended in this range", anything else "Not provided by
|
||||
this server version".
|
||||
|
||||
### D9. Total duration tile
|
||||
|
||||
The Egress page tile is labelled "Total duration", with `INFO_TEXTS.egressTotalDuration`.
|
||||
|
||||
### D10. Browser labels and the OS label
|
||||
|
||||
`format/labels.ts` gains:
|
||||
- `BROWSER_LABELS` for `chrome`, `firefox`, `safari`, `edge`, `none` ("No browser") and `other`, used by the browser
|
||||
donut, which today prints the key;
|
||||
- `clientOsLabel(os)`, which compares the reported string, case-insensitively and trimmed, with the family names
|
||||
(`windows`, `macos`, `mac os`, `linux`, `android`, `ios`) and returns the `OS_LABELS` entry, or the string as
|
||||
reported.
|
||||
|
||||
The library exports both for the detail pages of every edition.
|
||||
|
||||
Rejected: mapping by substring as the writer does, which would turn "Chrome OS" into "Linux" on a page meant to show
|
||||
what the client reported.
|
||||
|
||||
### D11. A locked track route
|
||||
|
||||
`routes.ts` adds `tracks/:trackSid`:
|
||||
- with no navigation entry and `page.feature: 'participant-detail'`;
|
||||
- with the breadcrumb param `trackSid`, which titles the lock, as the route has no list above it;
|
||||
- opening a locked page that reuses the participant lock.
|
||||
|
||||
`exactIdTarget()` maps `track` to `/tracks`. An edition with the feature replaces the route by path, as it does for
|
||||
the other detail routes.
|
||||
|
||||
## API
|
||||
|
||||
No shape change: `storage.<collection>.oldest` exists and only covers more collections.
|
||||
|
||||
## Performance budget
|
||||
|
||||
About reads at most eleven point queries every 60 s, with the existing cache. Each is verified in the info integration
|
||||
test with `explain()`: an `IXSCAN` with one document examined, and a `COLLSCAN` only on `nodes`.
|
||||
|
||||
`track_stats` has no entry: its plan scans the buckets of the time series.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Rounding changes durations across the UI, by up to half a unit] → Unit tests of the formatter for both modes, and
|
||||
updated fixtures where a duration reads differently.
|
||||
- [A running duration that ends shows one second more than it last counted] → Accepted: the stored value is the
|
||||
measured one.
|
||||
- [The `none` browser key only appears in rollups written after the writer change] → Older rollups keep those sessions
|
||||
under `other`, which still reads "Other".
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Ships with the 3.10 dashboard. The browser label of `none` relies on the typings of `writer-review-fixes`, applied
|
||||
first.
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
A review against a real deployment, with OpenVidu Meet rooms, CLI participants, every egress output kind and an RTMP
|
||||
ingress, found shared UI pieces showing values the data does not hold. Short-range charts repeat their time labels,
|
||||
count charts tick at half participants and step lines drop their last bucket. Skeletons flash on fast loads, and ended
|
||||
durations are truncated. About leaves most "oldest" cells empty and has no total. The Analytics room averages blame the
|
||||
server version when no room has ended yet.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Time-series charts:**
|
||||
- x-axis ticks are never closer than the unit their labels show (one minute for `HH:mm`, one day for `MM-dd`), so a
|
||||
label never repeats;
|
||||
- the y-axis ticks at whole numbers when every value of the chart is an integer;
|
||||
- a step series holds its last value until the end of the range, so the last bucket is drawn.
|
||||
- **Skeletons** keep the space of what they replace but stay invisible for their first 300 ms: a fast response shows
|
||||
no flash.
|
||||
- **Durations:** ended durations are rounded to the smallest unit they show (2.7 s reads "3 s"); running durations
|
||||
keep counting whole elapsed units.
|
||||
- **About storage:**
|
||||
- a Total row with the documents and size of every listed collection;
|
||||
- the oldest document of every collection whose time field leads an index or that holds one document per node:
|
||||
participants, egresses, ingress sessions, daily rollups and nodes, besides the ones shown today;
|
||||
- `GET /api/v1/info` reports those dates;
|
||||
- tracks, subscriptions and stats keep "—" with a tooltip that says why.
|
||||
- **Footer:** the version is plain text, no longer a link to About.
|
||||
- **Analytics room averages:** with no room ended in the range, the Average room size and Average room duration tiles
|
||||
hint "No room ended in this range". "Not provided by this server version" stays for a range with ended rooms and no
|
||||
average.
|
||||
- **Egress page:** the "Recorded time" tile becomes "Total duration", with an info tip: from each egress request to
|
||||
its end, failed ones included.
|
||||
- **Client labels:**
|
||||
- browser keys show friendly labels (Chrome, Firefox, Safari, Edge, Other), and the key `none` reads "No browser";
|
||||
- a shared label maps a reported OS to its family label (`linux` reads "Linux") for every page that shows the raw
|
||||
value.
|
||||
- **Global search:** an exact track ID (`TR_` prefix) shows "Open track <ID>" and opens the track route, locked in CE
|
||||
like the other detail routes.
|
||||
|
||||
Scope excludes the following:
|
||||
- The writer and contract side (the `none` browser key, the ingress session's room, the egress status history):
|
||||
change `writer-review-fixes`.
|
||||
- Column widths of tables at 1440 px (backlog).
|
||||
- The Average room size rounding: it stays a whole number of participants.
|
||||
|
||||
Edition: CE. Repository: the dashboard (backend info service, UI library, specs).
|
||||
|
||||
CHANGELOG lines: none. These views have not shipped in a release, and the 3.10 lines already describe them.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
None.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `ui-building-blocks`: chart axes and last step, skeleton delay, rounded durations, track IDs in the global search
|
||||
and OS and browser labels.
|
||||
- `settings-about`: the storage table's Total row and its oldest cells.
|
||||
- `editions-info`: the oldest document of the collections whose time field is indexed.
|
||||
- `ui-shell`: the footer version is plain text, and the track route is locked in CE.
|
||||
- `analytics-ui`: the room averages' hint without ended rooms, and the browser donut's labels.
|
||||
- `egress-ui`: the Total duration tile.
|
||||
|
||||
## Impact
|
||||
|
||||
- Backend: `info/info-service.ts` (a time field per collection, the `{ scope, bucket }` index of the daily rollups),
|
||||
its unit and integration tests.
|
||||
- UI library:
|
||||
- `charts/chart-options.ts`;
|
||||
- `components/feedback/skeleton.ts`;
|
||||
- `format/formatters.ts` and `components/duration/`;
|
||||
- `format/labels.ts` (browser labels, OS label function);
|
||||
- the global search, `shell/sidenav-footer.ts`, the About tab, the Analytics page and the Egress page;
|
||||
- `routes.ts` (the locked `tracks/:trackSid` route);
|
||||
- unit and e2e tests.
|
||||
- No API shape change: `storage.<collection>.oldest` already exists.
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### 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 with `roomsEnded: 12` and 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; with `roomsEnded: 0` both tiles read "n/a" with the hint "No room ended in this range".
|
||||
|
||||
#### 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"
|
||||
|
||||
#### Scenario: No room ended yet
|
||||
|
||||
- **GIVEN** a summary with `roomsCreated: 2`, `roomsEnded: 0` and neither average
|
||||
- **WHEN** the section renders
|
||||
- **THEN** both tiles read "n/a" with the hint "No room ended in this range"
|
||||
|
||||
### 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 key shown by its shared friendly label 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
|
||||
|
||||
#### Scenario: Sessions without a browser
|
||||
|
||||
- **GIVEN** a browser breakdown with `chrome: 5` and `none: 5`
|
||||
- **WHEN** the browser donut renders
|
||||
- **THEN** its slices read "Chrome: 50.0 %" and "No browser: 50.0 %"
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Storage and database facts in info
|
||||
|
||||
Edition: CE. With a valid bearer token `GET /api/v1/info` SHALL include `database` (`version` of the MongoDB server
|
||||
and `timeseriesSupported`) and `storage`, one entry per analytics collection with `documents`, `storageBytes`,
|
||||
`indexBytes` and `oldest` (the earliest value of the collection's time field when an index leads with it, and of
|
||||
`firstSeenAt` for `nodes`), read with `$collStats` and cached for 60 seconds.
|
||||
|
||||
#### Scenario: Storage per collection
|
||||
|
||||
- **GIVEN** a database with 1 200 rooms and 20 000 raw events
|
||||
- **WHEN** the client requests `GET /api/v1/info` with a valid token
|
||||
- **THEN** `storage.rooms.documents` is `1200`, `storage.events.documents` is `20000`, every entry has `storageBytes`
|
||||
and `indexBytes`, and `database.version` is the server version string
|
||||
|
||||
#### Scenario: Oldest per collection
|
||||
|
||||
- **GIVEN** a database with documents in every analytics collection
|
||||
- **WHEN** the client requests `GET /api/v1/info` with a valid token
|
||||
- **THEN** `oldest` holds the earliest `createdAt` of `rooms` and `ingresses`, `joinedAt` of `participants`,
|
||||
`startedAt` of `egresses` and `ingress_sessions`, `ts` of `events`, `bucket` of the 5-minute and hourly rollups and
|
||||
of the `global` scope of the daily rollups, and `firstSeenAt` of `nodes`, while `tracks`, `track_subscriptions` and
|
||||
`track_stats` have no `oldest`
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Egress summary tiles
|
||||
|
||||
Edition: CE. The Egress page SHALL show four KPI tiles from the summary endpoint above the filters: Active, Started
|
||||
in range (hint: count by type, in the fixed order room composite, web, participant, track composite, track, with type
|
||||
labels and without zero counts), Failed in range (hint: aborted and limit reached) and Total duration (with an info
|
||||
tip); approximate values carry a tooltip saying the range exceeds the exact-count limit, and the tiles keep their last
|
||||
value while refreshing.
|
||||
|
||||
#### Scenario: Tiles after load
|
||||
|
||||
- **GIVEN** the summary answers `{ active: 3, startedInRange: 120, byType: { track: 20, room_composite: 100 },
|
||||
byEndStatus: { complete: 110, failed: 7, aborted: 2 }, totalDurationMs: 36000000, approximate: false }`
|
||||
- **WHEN** the page renders
|
||||
- **THEN** the tiles read 3, 120 with hint "Room composite 100 · Track 20", 7 with hint "Aborted 2", and "10 h 00 m"
|
||||
under "Total duration"
|
||||
|
||||
#### Scenario: Total duration explained
|
||||
|
||||
- **WHEN** the user hovers or focuses the info icon of the Total duration tile
|
||||
- **THEN** it reads "Time from each egress request to its end, failed egress included."
|
||||
|
||||
#### Scenario: Stable hint order
|
||||
|
||||
- **GIVEN** a refresh that answers `byType` with its keys in another order
|
||||
- **WHEN** the tile updates
|
||||
- **THEN** the hint keeps the order "Room composite · Track"
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Retention and storage
|
||||
Edition: CE. About SHALL show the retention per data class (events, stats, tracks and subscriptions, entities, 5-minute
|
||||
rollups, hourly rollups, daily rollups) as a table with one row per class in the shared retention length wording, and a
|
||||
storage table with collection, document count, size and oldest document as the info endpoint reports them, closed by a
|
||||
Total row; sizes use the shared byte formatter.
|
||||
|
||||
#### Scenario: Retention line
|
||||
- **GIVEN** the defaults
|
||||
- **WHEN** About renders
|
||||
- **THEN** the retention table has the rows Events 7 days, Stats 7 days, Tracks and subscriptions 7 days, Entities 90
|
||||
days, 5-minute rollups 7 days, Hourly rollups 400 days and Daily rollups "Kept forever"
|
||||
|
||||
#### Scenario: Retention that is not a whole number of days
|
||||
- **GIVEN** `retention.tracksMs` of 36 hours and `retention.rollupsDailyMs` of 1 825 days
|
||||
- **WHEN** About renders
|
||||
- **THEN** the row Tracks and subscriptions reads "36 hours" and the row Daily rollups reads "1,825 days"
|
||||
|
||||
#### Scenario: Storage total
|
||||
- **GIVEN** storage entries for `rooms` (4 documents, 381 kB with indexes) and `events` (285 documents, 356 kB with
|
||||
indexes)
|
||||
- **WHEN** About renders
|
||||
- **THEN** the last row of the storage table reads "Total", "289", "737 kB" and an empty Oldest cell
|
||||
|
||||
#### Scenario: Collection without an oldest date
|
||||
- **GIVEN** the `tracks` entry without `oldest`
|
||||
- **WHEN** About renders
|
||||
- **THEN** its Oldest cell reads "—" with the tooltip "Not shown: no index starts with this collection's time field"
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Time-series chart fixed to the range
|
||||
Edition: CE. The time-series chart SHALL render the given series between `from` and `to` in the given timezone with
|
||||
granularity buckets; the x-axis never extends beyond the range nor ticks closer than the unit of its labels, wheel and
|
||||
drag scroll the page, and no zoom component is enabled. It uses a shared tooltip keyed by x across the charts of a
|
||||
page, 2 px lines, one y-axis per chart, and the shared formatters for bytes, bitrates, durations and percentages.
|
||||
|
||||
#### Scenario: Wheel over a chart
|
||||
- **GIVEN** a chart in the page
|
||||
- **WHEN** the user scrolls the mouse wheel over it
|
||||
- **THEN** the page scrolls and the chart range is unchanged
|
||||
|
||||
#### Scenario: Sparse data
|
||||
- **GIVEN** buckets exist only for half of the range
|
||||
- **WHEN** the chart renders
|
||||
- **THEN** the x-axis spans `from` to `to` and missing buckets show gaps, not a shortened axis
|
||||
|
||||
#### Scenario: Labels of a short range
|
||||
- **GIVEN** a chart from 10:06:20 to 10:09:50 with one-minute buckets
|
||||
- **WHEN** it renders
|
||||
- **THEN** the x-axis labels read whole minutes, each one once (`10:07`, `10:08`, `10:09`), and a range of several
|
||||
days labelled `MM-dd` ticks at most once a day
|
||||
|
||||
#### Scenario: Integer values
|
||||
- **GIVEN** a series whose values are all integers, such as active participants 2, 3 and 2
|
||||
- **WHEN** the chart renders
|
||||
- **THEN** the y-axis ticks are whole numbers, never 0.5 or 2.5, and a chart holding a fractional value keeps
|
||||
fractional ticks
|
||||
|
||||
#### Scenario: Last step of a step series
|
||||
- **GIVEN** a step series whose last bucket starts at 10:06 with the value 1, in a range ending at 10:06:55
|
||||
- **WHEN** the chart renders
|
||||
- **THEN** the line holds 1 from 10:06 to 10:06:55 instead of ending at 10:06
|
||||
|
||||
### Requirement: Empty, skeleton and error states
|
||||
Edition: CE. Empty state SHALL show icon, title, hint and up to two actions ("Clear filters", "Widen range");
|
||||
skeletons match the height of what they replace, stay invisible for their first 300 ms and render only until the first
|
||||
response of what they replace; the error notice shows the message, a Retry action and the request id, inline in a
|
||||
card or at page level.
|
||||
|
||||
#### Scenario: No rooms match
|
||||
- **GIVEN** a filtered list returns no rows
|
||||
- **WHEN** the empty state renders
|
||||
- **THEN** it reads "No rooms match" with the hint "Change the filters or widen the time range.", offers "Clear filters" and "Widen range", and the layout height does not jump
|
||||
|
||||
#### Scenario: Fast first response
|
||||
- **GIVEN** a page whose first response arrives 120 ms after it opens
|
||||
- **WHEN** the page loads
|
||||
- **THEN** no skeleton bar is painted, the space of what it replaces is kept and the content appears without a layout
|
||||
jump
|
||||
|
||||
#### Scenario: Slow first response
|
||||
- **GIVEN** a page whose first response takes 2 s
|
||||
- **WHEN** the page loads
|
||||
- **THEN** the skeleton becomes visible 300 ms after the page opened and the content replaces it when the response
|
||||
arrives
|
||||
|
||||
### Requirement: Global search
|
||||
|
||||
Edition: CE. The global search SHALL debounce input and send no request below 2 characters, showing the hint "Type at
|
||||
least 2 characters". An exact ID (`RM_`, `PA_`, `EG_`, `IN_`, `TR_` prefix) shows "Open <entity> <ID>" first; Enter
|
||||
opens that detail route (locked in CE) keeping the current range. Other text shows grouped prefix results (rooms,
|
||||
participants, egress, ingress; up to five each) with "See all in …" links to the list filtered by `q`.
|
||||
|
||||
#### Scenario: Exact SID
|
||||
- **GIVEN** the user types `PA_x1y2` with the range `from=now-6h&to=now&tz=UTC`
|
||||
- **WHEN** the results panel opens and the user presses Enter
|
||||
- **THEN** its first option reads "Open participant PA_x1y2", the application navigates to `/participants/PA_x1y2`, and the Participants breadcrumb links to `/participants?from=now-6h&to=now&tz=UTC`
|
||||
|
||||
#### Scenario: Exact track ID
|
||||
- **GIVEN** the user types `TR_a2b3`
|
||||
- **WHEN** the results panel opens and the user presses Enter
|
||||
- **THEN** its first option reads "Open track TR_a2b3" and the application navigates to `/tracks/TR_a2b3`
|
||||
|
||||
#### Scenario: Prefix search
|
||||
- **GIVEN** the user types `dem`
|
||||
- **WHEN** results arrive
|
||||
- **THEN** rooms starting with "dem" are listed under Rooms with "See all in Rooms" linking to `/rooms?q=dem`
|
||||
|
||||
#### Scenario: One character
|
||||
- **GIVEN** the user types `d`
|
||||
- **WHEN** the debounce elapses
|
||||
- **THEN** no search request is sent and the hint reads "Type at least 2 characters"
|
||||
|
||||
#### Scenario: Clear the search
|
||||
- **GIVEN** the global search holding `dem` with its results open
|
||||
- **WHEN** the user activates the "Clear search" button
|
||||
- **THEN** the box is empty, the results close, the focus stays in the box and the button disappears
|
||||
|
||||
#### Scenario: Keyboard, placeholder and clear button
|
||||
- **WHEN** the global search renders at any header width
|
||||
- **THEN** it is keyboard navigable, its placeholder shows whole, without truncation, and while it holds text a clear
|
||||
button empties it and closes the results
|
||||
|
||||
### Requirement: Formatters
|
||||
|
||||
Edition: CE. Shared formatters SHALL render numbers with `Intl.NumberFormat('en')`, percentages with one decimal and a
|
||||
space before the sign ("96.4 %", donut labels included), bytes in decimal units with three significant digits and
|
||||
durations as `850 ms`, `42 s`, `12 m 05 s`, `1 h 02 m`, `3 d 4 h`, rounded to the smallest unit shown; timestamps
|
||||
SHALL read as the timestamp format scenario states.
|
||||
|
||||
#### Scenario: Bytes and duration
|
||||
- **GIVEN** 12345678 bytes and 3725000 ms
|
||||
- **WHEN** formatted
|
||||
- **THEN** they read "12.3 MB" and "1 h 02 m"
|
||||
|
||||
#### Scenario: Rounded durations
|
||||
- **GIVEN** durations of 2 700 ms, 59 600 ms and 3 569 600 ms
|
||||
- **WHEN** formatted
|
||||
- **THEN** they read "3 s", "1 m 00 s" and "59 m 30 s"
|
||||
|
||||
#### Scenario: Percent in a donut
|
||||
- **GIVEN** a connection success ratio of 0.9643 and a donut slice of 0.25
|
||||
- **WHEN** they are shown
|
||||
- **THEN** they read "96.4 %" and "25.0 %"
|
||||
|
||||
#### Scenario: Timestamp format
|
||||
|
||||
- **WHEN** a timestamp is formatted
|
||||
- **THEN** it reads `2026-10-13 12:31:05` (date omitted inside the current day of a 24 h range) with a `UTC` suffix
|
||||
when `tz=UTC`
|
||||
|
||||
### Requirement: Enum labels
|
||||
|
||||
Edition: CE. Every enum value outside a status badge SHALL show its friendly sentence-case label, in kind badges,
|
||||
chart categories and filters: egress type, SDK, OS, browser, connection type and participant kind; the browser key
|
||||
`none` reads "No browser". A value without a label shows the raw value, and an OS string a client reported reads as
|
||||
its family label when it names one. Status badges keep their lowercase words.
|
||||
|
||||
#### Scenario: Chart category and filter
|
||||
- **GIVEN** egresses of type `room_composite`
|
||||
- **WHEN** the egress type breakdown and the Type filter render
|
||||
- **THEN** both read "Room composite"
|
||||
|
||||
#### Scenario: Browser keys
|
||||
- **GIVEN** a browser breakdown with the keys `chrome`, `none` and `other`
|
||||
- **WHEN** its donut renders
|
||||
- **THEN** the slices read "Chrome", "No browser" and "Other"
|
||||
|
||||
#### Scenario: Reported OS
|
||||
- **GIVEN** the client OS strings `linux`, `Linux`, `macos` and `Chrome OS`
|
||||
- **WHEN** they are shown through the shared OS label
|
||||
- **THEN** they read "Linux", "Linux", "macOS" and "Chrome OS"
|
||||
|
||||
### Requirement: Live durations
|
||||
|
||||
Edition: CE. Every duration of something still running SHALL count up live from its start between refreshes, in whole
|
||||
elapsed units and ignoring any stored duration it carries; a total that includes a running part adds that part's
|
||||
elapsed time. An ended item shows its stored duration, or its end minus its start without one, and an item with
|
||||
neither a stored duration nor the times to compute one shows "—".
|
||||
|
||||
#### Scenario: Running total
|
||||
- **GIVEN** an ingress with a stored total duration of 1 h and a session publishing for 5 minutes
|
||||
- **WHEN** its total duration renders
|
||||
- **THEN** it reads "1 h 05 m" and keeps counting while the session runs
|
||||
|
||||
#### Scenario: Running and then ended
|
||||
- **GIVEN** an egress active for 2 700 ms
|
||||
- **WHEN** its duration renders
|
||||
- **THEN** it reads "2 s", and once the egress ends with a stored duration of 2 700 ms it reads "3 s"
|
||||
|
||||
#### Scenario: Nothing to compute
|
||||
- **GIVEN** a row with no stored duration and no start time
|
||||
- **WHEN** it renders
|
||||
- **THEN** its duration reads "—"
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Footer version line
|
||||
Edition: CE. The bottom of the sidenav SHALL show only the OpenVidu version ("OpenVidu {version}", the dashboard
|
||||
version, as plain text), the edition badge, a Documentation link and, when the server enables it, an OpenAPI link.
|
||||
In rail mode the sidenav bottom shows the short edition badge and a documentation icon link with a tooltip.
|
||||
|
||||
#### Scenario: Version line reflects the info endpoint
|
||||
- **GIVEN** `GET /api/v1/info` reports dashboard 3.10.0, edition CE, server 3.10.0, schema 1 and `openapiDocsEnabled: false`
|
||||
- **WHEN** the shell renders on a Large viewport
|
||||
- **THEN** the sidenav bottom reads "OpenVidu 3.10.0", not a link, with the COMMUNITY badge and a Documentation link, and shows neither "server" nor "schema"
|
||||
|
||||
#### Scenario: OpenAPI link in development
|
||||
- **GIVEN** `openapiDocsEnabled: true`
|
||||
- **WHEN** the shell renders
|
||||
- **THEN** an OpenAPI link to `api/v1/docs` follows the Documentation link
|
||||
|
||||
### Requirement: Locked PRO features in CE
|
||||
Edition: CE. PRO entries, links and sections SHALL stay visible in CE and render a lock with the feature title, a short
|
||||
explanation and a "Learn about OpenVidu PRO" link. The lock is decided from the feature flags of `GET /api/v1/info` and
|
||||
makes no request to the locked endpoint. Full-page locks keep header and breadcrumbs over a dimmed, non-interactive
|
||||
silhouette of three tiles and one block, with no refresh control in the header; in-card locks size to their message.
|
||||
|
||||
#### Scenario: Room detail in CE
|
||||
- **GIVEN** the info endpoint does not report the feature `room-detail`
|
||||
- **WHEN** the user follows a room SID link of the Rooms list
|
||||
- **THEN** `/rooms/RM_ab12cd` shows the room header from the list data and the full-page lock, and no detail request is sent
|
||||
|
||||
#### Scenario: Track route in CE
|
||||
- **GIVEN** the info endpoint does not report the feature `participant-detail`
|
||||
- **WHEN** the user opens `/tracks/TR_a2b3`, as the global search does for an exact track ID
|
||||
- **THEN** the page shows the full-page lock titled `TR_a2b3`, and no request about the track is sent
|
||||
|
||||
#### Scenario: Locked sidenav entry
|
||||
- **GIVEN** a sidenav entry whose route declares a feature that `GET /api/v1/info` does not report
|
||||
- **WHEN** the sidenav renders
|
||||
- **THEN** the entry shows a lock icon with a tooltip and opens the full-page lock; no sidenav entry of the CE routes declares a feature, so the CE sidenav shows no lock
|
||||
|
||||
#### Scenario: Compact in-card lock
|
||||
- **GIVEN** the CE Analytics page
|
||||
- **WHEN** its PRO cards render
|
||||
- **THEN** each locked card is as tall as its lock message, not as the chart it replaces
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
# Tasks
|
||||
|
||||
## 1. Backend
|
||||
|
||||
- [x] 1.1 Replace the index discovery of `InfoService.storageEntry()` with the time field map of design D6 (the
|
||||
`global` scope for `analytics_daily`, `firstSeenAt` for `nodes`, no entry for `tracks`,
|
||||
`track_subscriptions` and `track_stats`). Verify with the info unit tests and an integration test of "Oldest per collection" that
|
||||
runs `explain()` on every query: one document examined, `COLLSCAN` only on `nodes`.
|
||||
|
||||
## 2. UI library
|
||||
|
||||
- [x] 2.1 Set the x-axis `minInterval` from the label unit, the y-axis `minInterval: 1` for all-integer data and the
|
||||
closing point of step series in `timeSeriesOption()`. Verify with chart-options unit tests of the three scenarios
|
||||
of "Time-series chart fixed to the range".
|
||||
- [x] 2.2 Make `ovd-skeleton` invisible for its first 300 ms while keeping its height, and drop the fade under
|
||||
`prefers-reduced-motion`. Verify with a component test of the delay and an e2e check that a fast mocked response
|
||||
paints no skeleton bar.
|
||||
- [x] 2.3 Round `formatDuration()` to the smallest unit shown, add the `floor` mode and use it in `ovd-duration` while
|
||||
running. Verify with formatter unit tests of "Rounded durations" and a duration component test of "Running and
|
||||
then ended".
|
||||
- [x] 2.4 Add `BROWSER_LABELS` and `clientOsLabel()` to `format/labels.ts`, export them, and label the browser donut
|
||||
and its table with them. Verify with label unit tests of "Browser keys" and "Reported OS" and an Analytics
|
||||
component test of "Sessions without a browser".
|
||||
- [x] 2.5 Render the footer version as plain text. Verify with the sidenav footer test.
|
||||
- [x] 2.6 Hint "No room ended in this range" on both room averages when `roomsEnded` is 0. Verify with Analytics
|
||||
component tests of "No room ended yet" and "Missing average".
|
||||
- [x] 2.7 Rename the Egress page tile "Total duration" with `INFO_TEXTS.egressTotalDuration`. Verify with the Egress
|
||||
page test of the tiles and the info tip.
|
||||
- [x] 2.8 Add the Total row and the "—" tooltip to the About storage table. Verify with About tab tests of "Storage
|
||||
total" and "Collection without an oldest date".
|
||||
- [x] 2.9 Add the locked `tracks/:trackSid` route, and map `track` in `exactIdTarget()` to it. Verify with a global
|
||||
search test of "Exact track ID" and an e2e test of "Track route in CE".
|
||||
- [x] 2.10 Space the media statistics notice of the Data transferred card from the tiles below it. Verify by a
|
||||
screenshot of an Analytics range with partial media statistics.
|
||||
|
||||
## 3. End-to-end and docs
|
||||
|
||||
- [x] 3.1 Update the e2e fixtures and screenshots whose durations or labels change, and run `./dashboard.sh e2e`.
|
||||
Verify that it passes.
|
||||
- [x] 3.2 Update the library README and `AGENTS.md` notes that list the exported labels or the routes, if any. Verify by
|
||||
reading them against the change.
|
||||
|
||||
## 4. Integration check
|
||||
|
||||
- [x] 4.1 Run `./dashboard.sh check` and `./dashboard.sh test-integration`. Verify that both pass.
|
||||
- [x] 4.2 On a local deployment with `./dashboard.sh dev`, check an Analytics chart of a 15-minute custom range, the
|
||||
About table and a fast page load against the scenarios. Verify by screenshots.
|
||||
|
|
@ -94,7 +94,7 @@ formatters, show "n/a" with a hint for a value the summary omits and keep the la
|
|||
|
||||
#### Scenario: Missing average
|
||||
|
||||
- **GIVEN** a summary without `avgRoomSize`
|
||||
- **GIVEN** a summary with `roomsEnded: 12` and 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"
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ formatters, show "n/a" with a hint for a value the summary omits and keep the la
|
|||
|
||||
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.
|
||||
format) from the summary; with `roomsEnded: 0` both tiles read "n/a" with the hint "No room ended in this range".
|
||||
|
||||
#### Scenario: Rooms over 24 hours
|
||||
|
||||
|
|
@ -111,6 +111,12 @@ format) from the summary.
|
|||
- **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"
|
||||
|
||||
#### Scenario: No room ended yet
|
||||
|
||||
- **GIVEN** a summary with `roomsCreated: 2`, `roomsEnded: 0` and neither average
|
||||
- **WHEN** the section renders
|
||||
- **THEN** both tiles read "n/a" with the hint "No room ended in this range"
|
||||
|
||||
### Requirement: Participants section
|
||||
|
||||
Edition: CE. The Participants section SHALL show `participantsJoined` and `participantsAborted` as lines,
|
||||
|
|
@ -141,8 +147,8 @@ granularity is `1d`, otherwise a hint that unique identities are available at da
|
|||
|
||||
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 %").
|
||||
listing key, count and percent, every key shown by its shared friendly label and every percent in the shared percent
|
||||
format ("62.8 %").
|
||||
|
||||
#### Scenario: Nine SDKs
|
||||
|
||||
|
|
@ -162,6 +168,12 @@ names as stored) and every percent in the shared percent format ("62.8 %").
|
|||
- **WHEN** the donut and its table render
|
||||
- **THEN** the known key reads its friendly label and the other key reads as stored
|
||||
|
||||
#### Scenario: Sessions without a browser
|
||||
|
||||
- **GIVEN** a browser breakdown with `chrome: 5` and `none: 5`
|
||||
- **WHEN** the browser donut renders
|
||||
- **THEN** its slices read "Chrome: 50.0 %" and "No browser: 50.0 %"
|
||||
|
||||
### Requirement: Bandwidth section
|
||||
|
||||
Edition: CE. The Bandwidth section, headed "Data transferred", SHALL show `bytesUp` and `bytesDown` per bucket in the
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ unlimited) SHALL be read from `schema_meta` on every call; when the writer has n
|
|||
|
||||
Edition: CE. With a valid bearer token `GET /api/v1/info` SHALL include `database` (`version` of the MongoDB server
|
||||
and `timeseriesSupported`) and `storage`, one entry per analytics collection with `documents`, `storageBytes`,
|
||||
`indexBytes` and `oldest` (earliest `ts`, `bucket` or `createdAt`, when cheap to obtain), read with `$collStats`
|
||||
and cached for 60 seconds.
|
||||
`indexBytes` and `oldest` (the earliest value of the collection's time field when an index leads with it, and of
|
||||
`firstSeenAt` for `nodes`), read with `$collStats` and cached for 60 seconds.
|
||||
|
||||
#### Scenario: Storage per collection
|
||||
|
||||
|
|
@ -90,6 +90,15 @@ and cached for 60 seconds.
|
|||
- **THEN** `storage.rooms.documents` is `1200`, `storage.events.documents` is `20000`, every entry has `storageBytes`
|
||||
and `indexBytes`, and `database.version` is the server version string
|
||||
|
||||
#### Scenario: Oldest per collection
|
||||
|
||||
- **GIVEN** a database with documents in every analytics collection
|
||||
- **WHEN** the client requests `GET /api/v1/info` with a valid token
|
||||
- **THEN** `oldest` holds the earliest `createdAt` of `rooms` and `ingresses`, `joinedAt` of `participants`,
|
||||
`startedAt` of `egresses` and `ingress_sessions`, `ts` of `events`, `bucket` of the 5-minute and hourly rollups and
|
||||
of the `global` scope of the daily rollups, and `firstSeenAt` of `nodes`, while `tracks`, `track_subscriptions` and
|
||||
`track_stats` have no `oldest`
|
||||
|
||||
### Requirement: Public subset of info
|
||||
|
||||
Edition: CE. Without an `Authorization` header `GET /api/v1/info` SHALL answer `200` with only
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@ the ranked-sort limit, which falls back to `startedAt`.
|
|||
### Requirement: Egress summary tiles
|
||||
|
||||
Edition: CE. The Egress page SHALL show four KPI tiles from the summary endpoint above the filters: Active, Started
|
||||
in range (hint: count by type, in the fixed type order room composite, web, participant, track composite, track, with
|
||||
type labels and without zero counts), Failed in range (hint: aborted and limit reached) and Recorded time (total
|
||||
duration); approximate values carry a tooltip saying the range exceeds the exact-count limit, and the tiles keep their
|
||||
last value while refreshing.
|
||||
in range (hint: count by type, in the fixed order room composite, web, participant, track composite, track, with type
|
||||
labels and without zero counts), Failed in range (hint: aborted and limit reached) and Total duration (with an info
|
||||
tip); approximate values carry a tooltip saying the range exceeds the exact-count limit, and the tiles keep their last
|
||||
value while refreshing.
|
||||
|
||||
#### Scenario: Tiles after load
|
||||
|
||||
|
|
@ -64,6 +64,12 @@ last value while refreshing.
|
|||
byEndStatus: { complete: 110, failed: 7, aborted: 2 }, totalDurationMs: 36000000, approximate: false }`
|
||||
- **WHEN** the page renders
|
||||
- **THEN** the tiles read 3, 120 with hint "Room composite 100 · Track 20", 7 with hint "Aborted 2", and "10 h 00 m"
|
||||
under "Total duration"
|
||||
|
||||
#### Scenario: Total duration explained
|
||||
|
||||
- **WHEN** the user hovers or focuses the info icon of the Total duration tile
|
||||
- **THEN** it reads "Time from each egress request to its end, failed egress included."
|
||||
|
||||
#### Scenario: Stable hint order
|
||||
|
||||
|
|
|
|||
|
|
@ -91,9 +91,9 @@ time, or failed with its message); once the page is reloaded after the migration
|
|||
|
||||
### Requirement: Retention and storage
|
||||
Edition: CE. About SHALL show the retention per data class (events, stats, tracks and subscriptions, entities, 5-minute
|
||||
rollups, hourly rollups, daily rollups) as a table with one row per class, each value in the shared retention length
|
||||
wording, and a storage table with collection, document count, size and oldest document, as reported by the info
|
||||
endpoint; sizes use the shared byte formatter.
|
||||
rollups, hourly rollups, daily rollups) as a table with one row per class in the shared retention length wording, and a
|
||||
storage table with collection, document count, size and oldest document as the info endpoint reports them, closed by a
|
||||
Total row; sizes use the shared byte formatter.
|
||||
|
||||
#### Scenario: Retention line
|
||||
- **GIVEN** the defaults
|
||||
|
|
@ -106,6 +106,17 @@ endpoint; sizes use the shared byte formatter.
|
|||
- **WHEN** About renders
|
||||
- **THEN** the row Tracks and subscriptions reads "36 hours" and the row Daily rollups reads "1,825 days"
|
||||
|
||||
#### Scenario: Storage total
|
||||
- **GIVEN** storage entries for `rooms` (4 documents, 381 kB with indexes) and `events` (285 documents, 356 kB with
|
||||
indexes)
|
||||
- **WHEN** About renders
|
||||
- **THEN** the last row of the storage table reads "Total", "289", "737 kB" and an empty Oldest cell
|
||||
|
||||
#### Scenario: Collection without an oldest date
|
||||
- **GIVEN** the `tracks` entry without `oldest`
|
||||
- **WHEN** About renders
|
||||
- **THEN** its Oldest cell reads "—" with the tooltip "Not shown: no index starts with this collection's time field"
|
||||
|
||||
### Requirement: Server info unavailable
|
||||
Edition: CE. When the info endpoint fails, About SHALL show the error notice "Server info unavailable" with a Retry
|
||||
action and the Preferences tab stays usable.
|
||||
|
|
|
|||
|
|
@ -188,9 +188,9 @@ tile itself carries no tooltip.
|
|||
|
||||
### Requirement: Time-series chart fixed to the range
|
||||
Edition: CE. The time-series chart SHALL render the given series between `from` and `to` in the given timezone with
|
||||
granularity buckets; the x-axis never extends beyond the range, wheel and drag scroll the page, and no zoom
|
||||
component is enabled. It uses a shared tooltip keyed by x across the charts of a page, 2 px lines, one y-axis
|
||||
per chart, and formats bytes, bitrates, durations and percentages with the shared formatters.
|
||||
granularity buckets; the x-axis never extends beyond the range nor ticks closer than the unit of its labels, wheel and
|
||||
drag scroll the page, and no zoom component is enabled. It uses a shared tooltip keyed by x across the charts of a
|
||||
page, 2 px lines, one y-axis per chart, and the shared formatters for bytes, bitrates, durations and percentages.
|
||||
|
||||
#### Scenario: Wheel over a chart
|
||||
- **GIVEN** a chart in the page
|
||||
|
|
@ -202,6 +202,23 @@ per chart, and formats bytes, bitrates, durations and percentages with the share
|
|||
- **WHEN** the chart renders
|
||||
- **THEN** the x-axis spans `from` to `to` and missing buckets show gaps, not a shortened axis
|
||||
|
||||
#### Scenario: Labels of a short range
|
||||
- **GIVEN** a chart from 10:06:20 to 10:09:50 with one-minute buckets
|
||||
- **WHEN** it renders
|
||||
- **THEN** the x-axis labels read whole minutes, each one once (`10:07`, `10:08`, `10:09`), and a range of several
|
||||
days labelled `MM-dd` ticks at most once a day
|
||||
|
||||
#### Scenario: Integer values
|
||||
- **GIVEN** a series whose values are all integers, such as active participants 2, 3 and 2
|
||||
- **WHEN** the chart renders
|
||||
- **THEN** the y-axis ticks are whole numbers, never 0.5 or 2.5, and a chart holding a fractional value keeps
|
||||
fractional ticks
|
||||
|
||||
#### Scenario: Last step of a step series
|
||||
- **GIVEN** a step series whose last bucket starts at 10:06 with the value 1, in a range ending at 10:06:55
|
||||
- **WHEN** the chart renders
|
||||
- **THEN** the line holds 1 from 10:06 to 10:06:55 instead of ending at 10:06
|
||||
|
||||
### Requirement: Chart markers
|
||||
Edition: CE. The chart SHALL draw event markers (join/leave, egress start/end, aborted) as dashed vertical lines,
|
||||
aborted connections in their own colour, at most 50 per chart; with more than 50 it draws none and shows "N events in
|
||||
|
|
@ -350,15 +367,28 @@ region and reserves its width, so the controls never move when it appears or cha
|
|||
- **THEN** the interval select or menu button stays visible but disabled, with the page's reason as its tooltip
|
||||
|
||||
### Requirement: Empty, skeleton and error states
|
||||
Edition: CE. Empty state SHALL show icon, title, hint and up to two actions ("Clear filters", "Widen range"); skeletons
|
||||
match the height of rows, tiles, charts or text they replace and render only until the first response of what they
|
||||
replace; the error notice shows the message, a Retry action and the request id, inline in a card or at page level.
|
||||
Edition: CE. Empty state SHALL show icon, title, hint and up to two actions ("Clear filters", "Widen range");
|
||||
skeletons match the height of what they replace, stay invisible for their first 300 ms and render only until the first
|
||||
response of what they replace; the error notice shows the message, a Retry action and the request id, inline in a
|
||||
card or at page level.
|
||||
|
||||
#### Scenario: No rooms match
|
||||
- **GIVEN** a filtered list returns no rows
|
||||
- **WHEN** the empty state renders
|
||||
- **THEN** it reads "No rooms match" with the hint "Change the filters or widen the time range.", offers "Clear filters" and "Widen range", and the layout height does not jump
|
||||
|
||||
#### Scenario: Fast first response
|
||||
- **GIVEN** a page whose first response arrives 120 ms after it opens
|
||||
- **WHEN** the page loads
|
||||
- **THEN** no skeleton bar is painted, the space of what it replaces is kept and the content appears without a layout
|
||||
jump
|
||||
|
||||
#### Scenario: Slow first response
|
||||
- **GIVEN** a page whose first response takes 2 s
|
||||
- **WHEN** the page loads
|
||||
- **THEN** the skeleton becomes visible 300 ms after the page opened and the content replaces it when the response
|
||||
arrives
|
||||
|
||||
### Requirement: PRO lock overlay
|
||||
Edition: CE. The lock overlay SHALL take a feature key and render its title, explanation and docs link in a full-page
|
||||
or card variant, make no network call, and keep the page's header and breadcrumbs in the full-page variant. The
|
||||
|
|
@ -404,8 +434,8 @@ button.
|
|||
### Requirement: Global search
|
||||
|
||||
Edition: CE. The global search SHALL debounce input and send no request below 2 characters, showing the hint "Type at
|
||||
least 2 characters". An exact ID (`RM_`, `PA_`, `EG_`, `IN_` prefix) shows "Open <entity> <ID>" as the first option;
|
||||
Enter opens that detail route (locked in CE) keeping the current range. Other text shows grouped prefix results (rooms,
|
||||
least 2 characters". An exact ID (`RM_`, `PA_`, `EG_`, `IN_`, `TR_` prefix) shows "Open <entity> <ID>" first; Enter
|
||||
opens that detail route (locked in CE) keeping the current range. Other text shows grouped prefix results (rooms,
|
||||
participants, egress, ingress; up to five each) with "See all in …" links to the list filtered by `q`.
|
||||
|
||||
#### Scenario: Exact SID
|
||||
|
|
@ -413,6 +443,11 @@ participants, egress, ingress; up to five each) with "See all in …" links to t
|
|||
- **WHEN** the results panel opens and the user presses Enter
|
||||
- **THEN** its first option reads "Open participant PA_x1y2", the application navigates to `/participants/PA_x1y2`, and the Participants breadcrumb links to `/participants?from=now-6h&to=now&tz=UTC`
|
||||
|
||||
#### Scenario: Exact track ID
|
||||
- **GIVEN** the user types `TR_a2b3`
|
||||
- **WHEN** the results panel opens and the user presses Enter
|
||||
- **THEN** its first option reads "Open track TR_a2b3" and the application navigates to `/tracks/TR_a2b3`
|
||||
|
||||
#### Scenario: Prefix search
|
||||
- **GIVEN** the user types `dem`
|
||||
- **WHEN** results arrive
|
||||
|
|
@ -485,14 +520,19 @@ Edition: CE. Toasts SHALL be Material snackbars shown for 3 s in a polite live r
|
|||
|
||||
Edition: CE. Shared formatters SHALL render numbers with `Intl.NumberFormat('en')`, percentages with one decimal and a
|
||||
space before the sign ("96.4 %", donut labels included), bytes in decimal units with three significant digits and
|
||||
durations as `850 ms`, `42 s`, `12 m 05 s`, `1 h 02 m`, `3 d 4 h`; timestamps SHALL read as the timestamp format
|
||||
scenario states.
|
||||
durations as `850 ms`, `42 s`, `12 m 05 s`, `1 h 02 m`, `3 d 4 h`, rounded to the smallest unit shown; timestamps
|
||||
SHALL read as the timestamp format scenario states.
|
||||
|
||||
#### Scenario: Bytes and duration
|
||||
- **GIVEN** 12345678 bytes and 3725000 ms
|
||||
- **WHEN** formatted
|
||||
- **THEN** they read "12.3 MB" and "1 h 02 m"
|
||||
|
||||
#### Scenario: Rounded durations
|
||||
- **GIVEN** durations of 2 700 ms, 59 600 ms and 3 569 600 ms
|
||||
- **WHEN** formatted
|
||||
- **THEN** they read "3 s", "1 m 00 s" and "59 m 30 s"
|
||||
|
||||
#### Scenario: Percent in a donut
|
||||
- **GIVEN** a connection success ratio of 0.9643 and a donut slice of 0.25
|
||||
- **WHEN** they are shown
|
||||
|
|
@ -677,14 +717,25 @@ its entry animation; the entry animation runs only on the first render, and neve
|
|||
### Requirement: Enum labels
|
||||
|
||||
Edition: CE. Every enum value outside a status badge SHALL show its friendly sentence-case label, in kind badges,
|
||||
chart categories and filters: egress type, SDK, OS, connection type and participant kind. A value without a label
|
||||
shows the raw value. Status badges keep their lowercase words.
|
||||
chart categories and filters: egress type, SDK, OS, browser, connection type and participant kind; the browser key
|
||||
`none` reads "No browser". A value without a label shows the raw value, and an OS string a client reported reads as
|
||||
its family label when it names one. Status badges keep their lowercase words.
|
||||
|
||||
#### Scenario: Chart category and filter
|
||||
- **GIVEN** egresses of type `room_composite`
|
||||
- **WHEN** the egress type breakdown and the Type filter render
|
||||
- **THEN** both read "Room composite"
|
||||
|
||||
#### Scenario: Browser keys
|
||||
- **GIVEN** a browser breakdown with the keys `chrome`, `none` and `other`
|
||||
- **WHEN** its donut renders
|
||||
- **THEN** the slices read "Chrome", "No browser" and "Other"
|
||||
|
||||
#### Scenario: Reported OS
|
||||
- **GIVEN** the client OS strings `linux`, `Linux`, `macos` and `Chrome OS`
|
||||
- **WHEN** they are shown through the shared OS label
|
||||
- **THEN** they read "Linux", "Linux", "macOS" and "Chrome OS"
|
||||
|
||||
### Requirement: Reconciler estimate text
|
||||
|
||||
Edition: CE. Every value estimated by the reconciler SHALL be explained with one text, "Closed by the reconciler: the
|
||||
|
|
@ -708,16 +759,21 @@ active zone.
|
|||
|
||||
### Requirement: Live durations
|
||||
|
||||
Edition: CE. Every duration of something still running SHALL count up live from its start between refreshes, ignoring
|
||||
any stored duration it carries; a total that includes a running part adds that part's elapsed time. An ended item shows
|
||||
its stored duration, or its end minus its start without one, and an item with neither a stored duration nor the times
|
||||
to compute one shows "—".
|
||||
Edition: CE. Every duration of something still running SHALL count up live from its start between refreshes, in whole
|
||||
elapsed units and ignoring any stored duration it carries; a total that includes a running part adds that part's
|
||||
elapsed time. An ended item shows its stored duration, or its end minus its start without one, and an item with
|
||||
neither a stored duration nor the times to compute one shows "—".
|
||||
|
||||
#### Scenario: Running total
|
||||
- **GIVEN** an ingress with a stored total duration of 1 h and a session publishing for 5 minutes
|
||||
- **WHEN** its total duration renders
|
||||
- **THEN** it reads "1 h 05 m" and keeps counting while the session runs
|
||||
|
||||
#### Scenario: Running and then ended
|
||||
- **GIVEN** an egress active for 2 700 ms
|
||||
- **WHEN** its duration renders
|
||||
- **THEN** it reads "2 s", and once the egress ends with a stored duration of 2 700 ms it reads "3 s"
|
||||
|
||||
#### Scenario: Nothing to compute
|
||||
- **GIVEN** a row with no stored duration and no start time
|
||||
- **WHEN** it renders
|
||||
|
|
|
|||
|
|
@ -146,13 +146,13 @@ the trail.
|
|||
|
||||
### Requirement: Footer version line
|
||||
Edition: CE. The bottom of the sidenav SHALL show only the OpenVidu version ("OpenVidu {version}", the dashboard
|
||||
version, linking to About), the edition badge, a Documentation link and, when the server enables it, an OpenAPI link.
|
||||
version, as plain text), the edition badge, a Documentation link and, when the server enables it, an OpenAPI link.
|
||||
In rail mode the sidenav bottom shows the short edition badge and a documentation icon link with a tooltip.
|
||||
|
||||
#### Scenario: Version line reflects the info endpoint
|
||||
- **GIVEN** `GET /api/v1/info` reports dashboard 3.10.0, edition CE, server 3.10.0, schema 1 and `openapiDocsEnabled: false`
|
||||
- **WHEN** the shell renders on a Large viewport
|
||||
- **THEN** the sidenav bottom reads "OpenVidu 3.10.0" with the COMMUNITY badge and a Documentation link, and shows neither "server" nor "schema"
|
||||
- **THEN** the sidenav bottom reads "OpenVidu 3.10.0", not a link, with the COMMUNITY badge and a Documentation link, and shows neither "server" nor "schema"
|
||||
|
||||
#### Scenario: OpenAPI link in development
|
||||
- **GIVEN** `openapiDocsEnabled: true`
|
||||
|
|
@ -238,6 +238,11 @@ silhouette of three tiles and one block, with no refresh control in the header;
|
|||
- **WHEN** the user follows a room SID link of the Rooms list
|
||||
- **THEN** `/rooms/RM_ab12cd` shows the room header from the list data and the full-page lock, and no detail request is sent
|
||||
|
||||
#### Scenario: Track route in CE
|
||||
- **GIVEN** the info endpoint does not report the feature `participant-detail`
|
||||
- **WHEN** the user opens `/tracks/TR_a2b3`, as the global search does for an exact track ID
|
||||
- **THEN** the page shows the full-page lock titled `TR_a2b3`, and no request about the track is sent
|
||||
|
||||
#### Scenario: Locked sidenav entry
|
||||
- **GIVEN** a sidenav entry whose route declares a feature that `GET /api/v1/info` does not report
|
||||
- **WHEN** the sidenav renders
|
||||
|
|
|
|||
Loading…
Reference in New Issue