From c9077f6f7abcc7afc294f550338ce5352bac6e20 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 3 Nov 2026 23:59:57 +0100 Subject: [PATCH] openvidu-dashboard: archive ui-review-fixes, keep only the specs --- .../changes/ui-review-fixes/.openspec.yaml | 2 - .../changes/ui-review-fixes/design.md | 171 ------------------ .../changes/ui-review-fixes/proposal.md | 79 -------- .../specs/analytics-ui/spec.md | 78 -------- .../specs/editions-info/spec.md | 24 --- .../ui-review-fixes/specs/egress-ui/spec.md | 28 --- .../specs/settings-about/spec.md | 29 --- .../specs/ui-building-blocks/spec.md | 166 ----------------- .../ui-review-fixes/specs/ui-shell/spec.md | 42 ----- .../openspec/changes/ui-review-fixes/tasks.md | 47 ----- .../openspec/specs/analytics-ui/spec.md | 20 +- .../openspec/specs/editions-info/spec.md | 13 +- .../openspec/specs/egress-ui/spec.md | 14 +- .../openspec/specs/settings-about/spec.md | 17 +- .../openspec/specs/ui-building-blocks/spec.md | 88 +++++++-- .../openspec/specs/ui-shell/spec.md | 9 +- 16 files changed, 130 insertions(+), 697 deletions(-) delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/.openspec.yaml delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/design.md delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/proposal.md delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/specs/analytics-ui/spec.md delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/specs/editions-info/spec.md delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/specs/egress-ui/spec.md delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/specs/settings-about/spec.md delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/specs/ui-building-blocks/spec.md delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/specs/ui-shell/spec.md delete mode 100644 openvidu-dashboard/openspec/changes/ui-review-fixes/tasks.md diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/.openspec.yaml b/openvidu-dashboard/openspec/changes/ui-review-fixes/.openspec.yaml deleted file mode 100644 index ac359ee85..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/.openspec.yaml +++ /dev/null @@ -1,2 +0,0 @@ -schema: spec-driven -created: 2026-11-03 diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/design.md b/openvidu-dashboard/openspec/changes/ui-review-fixes/design.md deleted file mode 100644 index 3e20ae0a9..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/design.md +++ /dev/null @@ -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..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. diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/proposal.md b/openvidu-dashboard/openspec/changes/ui-review-fixes/proposal.md deleted file mode 100644 index 7a1870549..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/proposal.md +++ /dev/null @@ -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 " 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..oldest` already exists. diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/analytics-ui/spec.md b/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/analytics-ui/spec.md deleted file mode 100644 index 55c30de36..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/analytics-ui/spec.md +++ /dev/null @@ -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 %" diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/editions-info/spec.md b/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/editions-info/spec.md deleted file mode 100644 index 5d650c805..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/editions-info/spec.md +++ /dev/null @@ -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` diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/egress-ui/spec.md b/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/egress-ui/spec.md deleted file mode 100644 index c63f5df8b..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/egress-ui/spec.md +++ /dev/null @@ -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" diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/settings-about/spec.md b/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/settings-about/spec.md deleted file mode 100644 index 0013449d9..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/settings-about/spec.md +++ /dev/null @@ -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" diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/ui-building-blocks/spec.md b/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/ui-building-blocks/spec.md deleted file mode 100644 index ae6ebf3b8..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/ui-building-blocks/spec.md +++ /dev/null @@ -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 " 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 "—" diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/ui-shell/spec.md b/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/ui-shell/spec.md deleted file mode 100644 index db47d2b62..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/specs/ui-shell/spec.md +++ /dev/null @@ -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 diff --git a/openvidu-dashboard/openspec/changes/ui-review-fixes/tasks.md b/openvidu-dashboard/openspec/changes/ui-review-fixes/tasks.md deleted file mode 100644 index d88b7418c..000000000 --- a/openvidu-dashboard/openspec/changes/ui-review-fixes/tasks.md +++ /dev/null @@ -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. diff --git a/openvidu-dashboard/openspec/specs/analytics-ui/spec.md b/openvidu-dashboard/openspec/specs/analytics-ui/spec.md index 5f525b600..0491b7411 100644 --- a/openvidu-dashboard/openspec/specs/analytics-ui/spec.md +++ b/openvidu-dashboard/openspec/specs/analytics-ui/spec.md @@ -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 diff --git a/openvidu-dashboard/openspec/specs/editions-info/spec.md b/openvidu-dashboard/openspec/specs/editions-info/spec.md index 52808bdb4..84a6705dd 100644 --- a/openvidu-dashboard/openspec/specs/editions-info/spec.md +++ b/openvidu-dashboard/openspec/specs/editions-info/spec.md @@ -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 diff --git a/openvidu-dashboard/openspec/specs/egress-ui/spec.md b/openvidu-dashboard/openspec/specs/egress-ui/spec.md index 2ef292e63..30831769f 100644 --- a/openvidu-dashboard/openspec/specs/egress-ui/spec.md +++ b/openvidu-dashboard/openspec/specs/egress-ui/spec.md @@ -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 diff --git a/openvidu-dashboard/openspec/specs/settings-about/spec.md b/openvidu-dashboard/openspec/specs/settings-about/spec.md index f0f132ec6..e61dcc012 100644 --- a/openvidu-dashboard/openspec/specs/settings-about/spec.md +++ b/openvidu-dashboard/openspec/specs/settings-about/spec.md @@ -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. diff --git a/openvidu-dashboard/openspec/specs/ui-building-blocks/spec.md b/openvidu-dashboard/openspec/specs/ui-building-blocks/spec.md index d10537363..18b648042 100644 --- a/openvidu-dashboard/openspec/specs/ui-building-blocks/spec.md +++ b/openvidu-dashboard/openspec/specs/ui-building-blocks/spec.md @@ -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 " 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 " 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 diff --git a/openvidu-dashboard/openspec/specs/ui-shell/spec.md b/openvidu-dashboard/openspec/specs/ui-shell/spec.md index 02322a37e..5458ef026 100644 --- a/openvidu-dashboard/openspec/specs/ui-shell/spec.md +++ b/openvidu-dashboard/openspec/specs/ui-shell/spec.md @@ -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