Skip to content

feat: improve readability of snapshots table#1664

Merged
Julusian merged 3 commits intoSofie-Automation:mainfrom
bbc:feat/snapshot-table
Mar 2, 2026
Merged

feat: improve readability of snapshots table#1664
Julusian merged 3 commits intoSofie-Automation:mainfrom
bbc:feat/snapshot-table

Conversation

@Julusian
Copy link
Copy Markdown
Member

@Julusian Julusian commented Feb 23, 2026

About the Contributor

This pull request is posted on behalf of the BBC

Type of Contribution

This is a: Feature

Current Behaviour

image

New Behavior

The snapshots table is tidied up a bit to make it easier to read:

image

The visible name of snapshots has been shorted to not be the full filename. Any existing snapshots will not be updated, so it wont immediately look this clean in all installations.

As this is not a user facing change (only visible to those who configure sofie), this has not been run through any UX work.

Testing

  • I have added one or more unit tests for this PR
  • I have updated the relevant unit tests
  • No unit test changes are needed for this PR

Affected areas

Time Frame

Other Information

Status

  • PR is ready to be reviewed.
  • The functionality has been tested by the author.
  • Relevant unit tests has been added / updated.
  • Relevant documentation (code comments, system documentation) has been added / updated.

Implementation Details

Data Model Enhancement

  • Added a new longname: string field to the SnapshotBase interface (and thus inherited by SnapshotSystem, SnapshotDebug, SnapshotRundownPlaylist) to preserve a detailed internal identifier for each snapshot.

Snapshot Name Restructuring

  • Snapshot creation separates display names from internal identifiers:
    • System snapshots: short public name (e.g. "System"), longname encodes system/studioId and timestamp (e.g. "System_studioId_timestamp").
    • Debug snapshots: public name like "Debug: ", longname preserves the previous timestamped pattern.
    • Rundown playlist snapshots: public name = playlist name, longname = "Rundown_<playlist.name>_<playlist.id>".

File Storage & Persistence

  • On-disk filenames and persisted documents now use snapshot.longname for storage; the shorter name is retained for UI display.
  • Snapshot documents written to the Snapshots collection now include the longname field.

UI Redesign

  • Redesigned snapshots table to improve readability:
    • Replaced the previous Type/Name/Comment table with rows showing a type badge + short name and a "When" column with relative timestamps (via MomentFromNow).
    • Added SnapshotRowItem component for per-row rendering and SnapshotTypeIndicator to show color-coded type badges.
    • Full longname is exposed via tooltip on the name link.
    • Per-row editing state (isExpanded) introduced, replacing the prior global editSnapshotId.
    • Existing RemoveSnapshotButton and "Show Remove" toggle behavior preserved, invoked per-row.

Other Code Changes

  • Removed the multilineText utility export/implementation from packages/webui/src/client/lib/lib.tsx as it became unused.
  • Added exported type aliases in useToggleExpandHelper.tsx:
    • ToggleSetExpanded and ToggleIsExpanded, and updated the hook's return typing to use them.

Tests

  • Updated tests (meteor/server/__tests__/cronjobs.test.ts, meteor/server/api/__tests__/cleanup.test.ts) to include the new longname field in inserted snapshot test data.

Public API / Type Changes

  • Public shape of snapshot objects now includes longname: string in addition to name. No other exported API signatures were removed.

@Julusian Julusian requested a review from a team as a code owner February 23, 2026 12:04
@Julusian Julusian added the Contribution from BBC Contributions sponsored by BBC (bbc.co.uk) label Feb 23, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 23, 2026

Walkthrough

Added a required longname field to snapshot types and persisted documents; switched on-disk snapshot filenames to use longname. Updated server snapshot creation to set both name (short/public) and longname (detailed). Refactored snapshots UI to per-row components with local expand/edit state and removed an unused multilineText helper.

Changes

Cohort / File(s) Summary
Schema & Types
packages/meteor-lib/src/collections/Snapshots.ts, packages/webui/src/client/ui/util/useToggleExpandHelper.tsx
Added longname: string to SnapshotBase. Exported ToggleSetExpanded and ToggleIsExpanded types and updated useToggleExpandHelper return typing.
Snapshot Creation & Persistence
meteor/server/api/snapshot.ts
Snapshot builders now produce both name (public) and longname (detailed). Persisted Snapshot documents include longname. Files written to disk use longname for filenames.
Tests
meteor/server/__tests__/cronjobs.test.ts, meteor/server/api/__tests__/cleanup.test.ts
Updated test fixtures/inserts to include longname: '' on Snapshot-like documents to satisfy new schema.
UI Refactor
packages/webui/src/client/ui/Settings/SnapshotsView.tsx
Replaced inline row rendering with SnapshotRowItem and SnapshotTypeIndicator; added per-row expand/edit state, adjusted columns (adds When), moved comment editing to row-level, preserved remove action per-row.
UI Cleanup
packages/webui/src/client/lib/lib.tsx
Removed exported multilineText function and its export.

Sequence Diagram(s)

sequenceDiagram
  participant UI as "Web UI"
  participant API as "Server API"
  participant SnapSvc as "Snapshot Service"
  participant DB as "Database"
  participant FS as "Filesystem"

  UI->>API: Trigger snapshot creation / request
  API->>SnapSvc: Build snapshot (produce name, longname, meta)
  SnapSvc->>DB: Insert Snapshot document (includes longname)
  SnapSvc->>FS: Write snapshot file (filename = longname)
  SnapSvc-->>API: Return snapshot object (name + longname)
  API-->>UI: Respond with snapshot list/item (name displayed, longname present)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through names both short and long,
I stitched a longname tidy and strong,
It lives on disk and fair in store,
The UI blooms with rows and more,
A little hop — snapshots hum along. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: improving readability of the snapshots table through UI redesign and shortened snapshot names.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 23, 2026

Codecov Report

❌ Patch coverage is 10.00000% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
meteor/server/api/snapshot.ts 0.00% 9 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
meteor/server/api/snapshot.ts (1)

785-789: ⚠️ Potential issue | 🟡 Minor

Downloaded snapshot filename uses the short name, which may lack uniqueness.

Line 787 sets the Content-Disposition attachment filename to snapshot.snapshot.name (e.g., "System.json" or "Debug: studioId.json"). Without a timestamp, multiple downloads could overwrite each other. Previously, name included the timestamp. Consider using longname for the download filename:

-		ctx.response.attachment(`${snapshot.snapshot.name}.json`)
+		ctx.response.attachment(`${snapshot.snapshot.longname}.json`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@meteor/server/api/snapshot.ts` around lines 785 - 789, The download filename
uses snapshot.snapshot.name which may not be unique; update the attachment call
to use the longer unique identifier (snapshot.snapshot.longname) instead of
snapshot.snapshot.name so returned files include the timestamp/unique part;
locate the code setting ctx.response.attachment in meteor/server/api/snapshot.ts
(the block that also sets ctx.response.type, ctx.response.status, and
ctx.response.body) and change the filename argument to
snapshot.snapshot.longname to prevent collisions.
🧹 Nitpick comments (1)
meteor/server/api/snapshot.ts (1)

228-234: getCurrentTime() called twice — potential timestamp mismatch between created and longname.

getCurrentTime() is called on line 231 for created and again on line 233 for the longname timestamp. These could return different values. Consider capturing it once:

Suggested fix
+		const now = getCurrentTime()
 		snapshot: {
 			_id: snapshotId,
 			type: SnapshotType.SYSTEM,
-			created: getCurrentTime(),
-			name: `System` + (studioId ? `_${studioId}` : ''),
-			longname: `System` + (studioId ? `_${studioId}` : '') + `_${formatDateTime(getCurrentTime())}`,
+			created: now,
+			name: `System` + (studioId ? `_${studioId}` : ''),
+			longname: `System` + (studioId ? `_${studioId}` : '') + `_${formatDateTime(now)}`,
 			version: CURRENT_SYSTEM_VERSION,
 		},

The same pattern applies to createDebugSnapshot (lines 288-290) and createRundownPlaylistSnapshot (lines 412-417).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@meteor/server/api/snapshot.ts` around lines 228 - 234, The code calls
getCurrentTime() multiple times when building snapshot objects, causing possible
mismatched timestamps; update the snapshot construction in snapshot.ts (and
similarly in createDebugSnapshot and createRundownPlaylistSnapshot) to call
getCurrentTime() once into a local const (e.g., const now = getCurrentTime())
and then use that variable for the created field and for formatDateTime(now)
when composing longname so both fields share the exact same timestamp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/meteor-lib/src/collections/Snapshots.ts`:
- Line 16: The Snapshot schema declares longname: string but old documents may
lack it, causing runtime undefined access (e.g., snapshot.snapshot.longname used
by fixValidPath and title={snapshot.longname}). Make longname optional in the
Snapshots type (longname?: string) and add defensive fallback logic wherever
it's used—replace direct accesses (snapshot.longname,
snapshot.snapshot.longname) with a helper/getter that returns longname || name
|| fileName before passing to fixValidPath or rendering title; alternatively
implement a one-off migration to backfill missing longname from name/fileName in
the database and update the Snapshots type accordingly.

In `@packages/webui/src/client/ui/Settings/SnapshotsView.tsx`:
- Around line 164-172: The anchor's title uses snapshot.longname which can be
undefined for older snapshots; update the title to fall back to snapshot.name
(e.g., title={snapshot.longname ?? snapshot.name}) so the tooltip never shows
"undefined" and still provides a useful label; modify the JSX in SnapshotsView
where the <a> element references snapshot.longname to use this fallback.
- Around line 214-225: SnapshotTypeIndicator currently renders hardcoded English
labels; update it to use the app i18n by calling useTranslation() inside
SnapshotTypeIndicator (or accept t as a prop) and replace the literal strings
"Playlist", "System", "Debug" (and the default badge text) with t(...) lookups
using appropriate translation keys (e.g. 'snapshot.type.playlist',
'snapshot.type.system', 'snapshot.type.debug' and a fallback key), ensuring
SnapshotTypeIndicator imports useTranslation from react-i18next and returns
translated badge labels for each SnapshotType case.
- Line 24: Update the inconsistent import in SnapshotsView.tsx: replace the
import of SnapshotItem and SnapshotType from
'@sofie-automation/meteor-lib/src/collections/Snapshots.js' with the matching
built artifact path under 'dist' (i.e. import from
'@sofie-automation/meteor-lib/dist/collections/Snapshots.js') so it aligns with
the other imports in this file and avoids runtime build errors.

---

Outside diff comments:
In `@meteor/server/api/snapshot.ts`:
- Around line 785-789: The download filename uses snapshot.snapshot.name which
may not be unique; update the attachment call to use the longer unique
identifier (snapshot.snapshot.longname) instead of snapshot.snapshot.name so
returned files include the timestamp/unique part; locate the code setting
ctx.response.attachment in meteor/server/api/snapshot.ts (the block that also
sets ctx.response.type, ctx.response.status, and ctx.response.body) and change
the filename argument to snapshot.snapshot.longname to prevent collisions.

---

Nitpick comments:
In `@meteor/server/api/snapshot.ts`:
- Around line 228-234: The code calls getCurrentTime() multiple times when
building snapshot objects, causing possible mismatched timestamps; update the
snapshot construction in snapshot.ts (and similarly in createDebugSnapshot and
createRundownPlaylistSnapshot) to call getCurrentTime() once into a local const
(e.g., const now = getCurrentTime()) and then use that variable for the created
field and for formatDateTime(now) when composing longname so both fields share
the exact same timestamp.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75cff0c and 3eb0dec.

📒 Files selected for processing (7)
  • meteor/server/__tests__/cronjobs.test.ts
  • meteor/server/api/__tests__/cleanup.test.ts
  • meteor/server/api/snapshot.ts
  • packages/meteor-lib/src/collections/Snapshots.ts
  • packages/webui/src/client/lib/lib.tsx
  • packages/webui/src/client/ui/Settings/SnapshotsView.tsx
  • packages/webui/src/client/ui/util/useToggleExpandHelper.tsx

Comment thread packages/meteor-lib/src/collections/Snapshots.ts
Comment thread packages/webui/src/client/ui/Settings/SnapshotsView.tsx Outdated
Comment thread packages/webui/src/client/ui/Settings/SnapshotsView.tsx
Comment thread packages/webui/src/client/ui/Settings/SnapshotsView.tsx
@Julusian Julusian force-pushed the feat/snapshot-table branch from 3eb0dec to 36962d9 Compare February 23, 2026 12:35
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
meteor/server/api/snapshot.ts (1)

232-233: Consider capturing getCurrentTime() once per snapshot to keep created and the timestamp in longname in sync.

In each of the three snapshot factories the created field and the longname suffix call getCurrentTime() independently:

created: getCurrentTime(),          // call 1
longname: `…_${formatDateTime(getCurrentTime())}`,  // call 2

While the gap is practically negligible (microseconds) and formatDateTime likely has second-level precision, the two values are semantically the same instant and should share one call.

♻️ Proposed refactor (shown for createSystemSnapshot; apply the same pattern to createDebugSnapshot and createRundownPlaylistSnapshot)
+	const now = getCurrentTime()
 	return {
 		…
 		snapshot: {
 			_id: snapshotId,
 			type: SnapshotType.SYSTEM,
-			created: getCurrentTime(),
+			created: now,
 			name: `System` + (studioId ? `_${studioId}` : ''),
-			longname: `System` + (studioId ? `_${studioId}` : '') + `_${formatDateTime(getCurrentTime())}`,
+			longname: `System` + (studioId ? `_${studioId}` : '') + `_${formatDateTime(now)}`,
 			version: CURRENT_SYSTEM_VERSION,
 		},

Also applies to: 289-290, 416-417

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@meteor/server/api/snapshot.ts` around lines 232 - 233, The created timestamp
and the timestamp used in longname are each calling getCurrentTime() separately
in createSystemSnapshot, createDebugSnapshot, and createRundownPlaylistSnapshot;
capture getCurrentTime() once per snapshot (e.g., const now = getCurrentTime())
and use that single value for the created field and for formatDateTime(now) when
building longname so both fields represent the identical instant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/webui/src/client/ui/Settings/SnapshotsView.tsx`:
- Around line 190-199: The code in SnapshotsView uses (snapshot.comment + '')
which yields the literal string "undefined" when comment is missing; update the
rendering to use a safe fallback such as (snapshot.comment || '') or the nullish
coalescing operator (snapshot.comment ?? '') before splitting so empty or
undefined comments produce an empty string, e.g., locate the span rendering in
the SnapshotsView component where snapshot.comment is processed and replace the
concatenation fallback with one of these safe fallbacks.
- Line 27: The import for assertNever is using the runtime-incorrect path
'@sofie-automation/corelib/src/lib.js'; update the import to pull from the
compiled package (e.g. '@sofie-automation/corelib/dist/lib' or
'@sofie-automation/corelib/dist/lib.js') so Settings/SnapshotsView.tsx imports
assertNever from the dist build like other files; locate the import line
referencing assertNever and replace the 'src/lib.js' path with the 'dist/lib'
equivalent.

---

Nitpick comments:
In `@meteor/server/api/snapshot.ts`:
- Around line 232-233: The created timestamp and the timestamp used in longname
are each calling getCurrentTime() separately in createSystemSnapshot,
createDebugSnapshot, and createRundownPlaylistSnapshot; capture getCurrentTime()
once per snapshot (e.g., const now = getCurrentTime()) and use that single value
for the created field and for formatDateTime(now) when building longname so both
fields represent the identical instant.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3eb0dec and 36962d9.

📒 Files selected for processing (7)
  • meteor/server/__tests__/cronjobs.test.ts
  • meteor/server/api/__tests__/cleanup.test.ts
  • meteor/server/api/snapshot.ts
  • packages/meteor-lib/src/collections/Snapshots.ts
  • packages/webui/src/client/lib/lib.tsx
  • packages/webui/src/client/ui/Settings/SnapshotsView.tsx
  • packages/webui/src/client/ui/util/useToggleExpandHelper.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/webui/src/client/lib/lib.tsx
  • meteor/server/tests/cronjobs.test.ts
  • meteor/server/api/tests/cleanup.test.ts

Comment thread packages/webui/src/client/ui/Settings/SnapshotsView.tsx Outdated
Comment thread packages/webui/src/client/ui/Settings/SnapshotsView.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/webui/src/client/ui/Settings/SnapshotsView.tsx (1)

9-9: Two separate imports from the same module — consider merging.

Lines 9 and 17 both import from ../../lib/lib.js. They can be combined into a single statement.

♻️ Proposed consolidation
-import { fetchFrom } from '../../lib/lib.js'
 ...
-import { hashSingleUseToken } from '../../lib/lib.js'
+import { fetchFrom, hashSingleUseToken } from '../../lib/lib.js'

Also applies to: 17-17

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/webui/src/client/ui/Settings/SnapshotsView.tsx` at line 9, There are
duplicate imports from ../../lib/lib.js in SnapshotsView.tsx; consolidate them
into a single import statement that includes fetchFrom plus the other symbol(s)
currently imported from the same module (i.e., replace the separate import lines
with one combined import for fetchFrom and the other exported names used in
Settings/SnapshotsView).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/webui/src/client/ui/Settings/SnapshotsView.tsx`:
- Around line 225-228: The default branch currently calls
assertNever(snapshotType) which throws at runtime and makes the subsequent
return unreachable; to make renders resilient to unknown persisted snapshot
types, flip the order in the switch's default case so you first render a safe
fallback (e.g., <span className="badge bg-primary">{snapshotType ??
'unknown'}</span>) and then call assertNever(snapshotType) for the compile-time
exhaustiveness check, keeping the assertNever(snapshotType) call present (it
should have signature (x: never) => never) so TypeScript still enforces
exhaustiveness while the UI gracefully handles unexpected values in the render
path.

---

Nitpick comments:
In `@packages/webui/src/client/ui/Settings/SnapshotsView.tsx`:
- Line 9: There are duplicate imports from ../../lib/lib.js in
SnapshotsView.tsx; consolidate them into a single import statement that includes
fetchFrom plus the other symbol(s) currently imported from the same module
(i.e., replace the separate import lines with one combined import for fetchFrom
and the other exported names used in Settings/SnapshotsView).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36962d9 and 7004eff.

📒 Files selected for processing (1)
  • packages/webui/src/client/ui/Settings/SnapshotsView.tsx

Comment thread packages/webui/src/client/ui/Settings/SnapshotsView.tsx
Copy link
Copy Markdown
Contributor

@tsorbo tsorbo left a comment

Choose a reason for hiding this comment

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

This is not a code review, but on behalf of NRK I think this looks like a good improvement for the snapshots overview.

@Julusian Julusian merged commit f1e15aa into Sofie-Automation:main Mar 2, 2026
23 of 24 checks passed
@Julusian Julusian deleted the feat/snapshot-table branch March 2, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Contribution from BBC Contributions sponsored by BBC (bbc.co.uk)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants