Skip to content

Modernize Jetpack VideoPress admin dashboard #48506

@dhasilva

Description

@dhasilva

Replace the legacy custom-component VideoPress admin dashboard (currently rooted at projects/packages/videopress/src/client/admin/) with a modern wp-build app under projects/packages/videopress/routes/dashboard/. The new dashboard covers four screens — Overview, Library, Video details, and Settings — and is built with @wordpress/components / @wordpress/ui, @wordpress/dataviews (for the Library), and @automattic/charts (for the Overview). No custom components except for video-domain pieces with no upstream equivalent (VideoThumbnail with timecode overlay, VideoPlayer) and Jetpack shell (AdminPage, JetpackFooter, ConnectionError).

Architecture mirrors projects/packages/activity-log/ — TanStack Query (@tanstack/react-query), class-initial-state.php bootstrap, REST controller proxying user-signed WPCOM requests, DataViews-driven listing, useProductCheckoutWorkflow for upsell. Reference: #48242.

All work hides behind the rsm_jetpack_ui_modernization_videopress filter (projects/packages/videopress/src/class-admin-ui.php). The legacy app keeps shipping until the cutover phase flips the filter default and removes the old code.

Approach

UI-first with mocked data, many small PRs. Each screen ships first as a UI-only PR with hardcoded fixture data and no-op handlers, so designers can check out the PR, enable the filter, and click around to validate the design without backend coupling. Wiring to real APIs lands in later PRs. The feature flag makes incremental, trunk-merging PRs safe.

Plan

  • Phase 0 — Feature flag + wp-build scaffold. Modernization filter rsm_jetpack_ui_modernization_videopress, routes/dashboard/{route,stage}.tsx stubs, wp-build-polyfills, branched menu render callback, build/watch pipeline integration. Landed in #48494.
  • Phase 1 — Shared chrome + Settings tab. AdminPage header, JetpackFooter, top-level tabs (Overview / Library / Settings) via wp-build sub-routes (routes/overview/, routes/library/, routes/settings/) wrapped in a shared DashboardLayout. Settings tab UI fully built in this phase (single "Restrict video access" toggle) — toggle state held in local React state with no persistence, validating the chrome end-to-end without backend coupling. Real persistence wires up in Phase 6. Other tabs stay placeholders. JITM slot (<div id="jp-admin-notices">) intentionally not ported — same as the Forms dashboard. Initial-state seeding deferred to Phase 5 (no client-side consumer in Phase 1). Also added sub-path exports ./admin-page and ./jetpack-footer to @automattic/jetpack-components to bypass dirty-SCSS in the package barrel under wp-build's esbuild + sass-plugin. Landed in VideoPress: Modernization Phase 1 — Shared chrome + Settings tab #48510.
  • Phase 2 — Library UI (mocked). DataViews-based video listing with @wordpress/dataviews. Hardcoded fixture array of mock videos. Bulk actions, search, filter, sort, pagination, view-mode toggle, and column/density config all driven by DataViews built-ins; handlers are no-ops at this stage. Upload button opens a stub modal. Floating bulk-action bar comes from DataViews' bulk-actions API. This is the largest UI PR. Landed in VideoPress: Modernization Phase 2 — Library tab (DataViews grid + table) #48586.
  • Phase 3 — Overview UI (mocked). KPI cards (Views / Visitors / Watch time, with delta indicators via @automattic/charts TrendIndicator), LineChart for the views-trends graph with comparison series, BarListChart (or LeaderboardChart) for "Most viewed", GeoChart + table for "Top locations". All driven by hardcoded fixture data. Date range picker: before implementing, verify whether @automattic/ui's DateRangeCalendar is published and usable directly; only fall back to porting Activity Log's picker (projects/packages/activity-log/src/js/components/DateRangePicker/) if not.
    Open in VideoPress: Modernization Phase 3 — Overview (mocked) #48682.

    Note (2026-05-11): Phase 7 discovery surfaced after this PR opened (see "Phase 7 discovery outcome" below). The two discovery-driven swaps — Visitors KPI → Impressions and Top locations card → Top videos by watch time — were folded into VideoPress: Modernization Phase 3 — Overview (mocked) #48682 directly (the UI-only PR is still pre-review, so the swaps land here rather than as a vestigial follow-up) along with the mock-data tuning needed to make the new metrics make sense.

  • Phase 4 — Video details UI (mocked). Form using @wordpress/components (InputControl, TextareaControl, ToggleControl, RadioControl, Modal). Breadcrumb header replacing the existing back-button. ⋯ menu in the header with Download file / Delete actions (Download file is new — wire the no-op stub). Card-based layout grouping (Privacy & sharing, Rating). Chapters helper modal preserved (rebuilt on @wordpress/components Modal if not already there); description stays a plain TextareaControl. Shortcode is kept (not in mocks but preserved to avoid functionality loss); rendered using the same WP primitives as the "Link to video" copy field. Open in VideoPress: Modernization Phase 4 — Video details (mocked) #48669.
  • Phase 5 — Initial_State + REST surface for existing endpoints. Seed class-initial-state.php with API root, nonce, and minimal capability flags (originally scoped into Phase 1 but deferred since Phase 1 had no client-side consumer). PHP REST controller (class-rest-controller.php) wrapping the existing endpoints used by the legacy dashboard: videopress/v1/settings, /wp/v2/media (filtered to videopress mime), /wpcom/v2/videopress/meta, /wpcom/v2/videopress/playback-jwt, videopress/v1/site, videopress/v1/features. TanStack Query hooks added alongside.
  • Phase 6 — Wire Library + Video details + Settings to real data. Replace fixture imports with TanStack Query hooks across the three screens that share the Phase 5 REST surface. Single PR.
  • Phase 7 — Discover & implement video-stats REST proxy. Identify WPCOM endpoint(s) for views, visitors, watch time, top videos, and top countries; document shape. Implement /jetpack/v4/videopress/stats/* proxies on the Activity Log pattern (user-signed Client::wpcom_json_api_request_as_user). May spawn a sub-issue if endpoint discovery requires WPCOM-side work.

    Note (2026-05-11): Discovery complete — proxy scope reduces to stats/video-plays?complete_stats=true (current + previous period). The geo and unique-visitor pieces of the original scope are storage-level gaps on WPCOM and have been turned into deferred follow-ups (see "Phase 7 discovery outcome" below). The UI swaps have already landed in VideoPress: Modernization Phase 3 — Overview (mocked) #48682. Phase 7 is now backend-only and is being combined with Phase 5 in a single PR on branch update/videopress-modernization-phase-7 (stacked on VideoPress: Modernization Phase 3 — Overview (mocked) #48682) — both phases share the REST controller / Initial State scaffolding, so landing them together avoids two passes over the same files.

  • Phase 8 — Wire Overview to real data. Replace fixture data sets with TanStack Query hooks against the new stats proxy. Date-range picker threads into after/before query params.

    Note (2026-05-11): Remains a separate PR landing after Phases 5+7. With the UI swaps already in VideoPress: Modernization Phase 3 — Overview (mocked) #48682 and the proxy + Initial State scaffolding planned for the combined Phase 5+7 PR, this phase reduces to the JS hook swap (replace useMockStats with a TanStack Query hook against the new proxy) plus threading the date-range picker into the proxy's query params.

  • Phase 9 — Cutover. Flip rsm_jetpack_ui_modernization_videopress default to true. Delete the legacy app at projects/packages/videopress/src/client/admin/, the legacy webpack entry, and the legacy enqueue branch in class-admin-ui.php. The wp-build path becomes the only path.

Phase 7 discovery outcome (2026-05-11)

The original Phase 3/7/8 plan above is preserved as-written so the rationale for the change is traceable. This section documents what the WPCOM-side investigation surfaced and the resulting design adjustments.

WPCOM-side investigation found two data gaps that change the Overview design relative to the mocks:

  1. Per-video geography is not collected. The video_stats table has no country / region columns, and stats/country-views + stats/location-views/{period} are site-wide (no post_id filter). There is no way to build a per-video "Top locations" card from existing data — this is a storage-level gap, not just a missing endpoint, and would require new collection + aggregation on the WPCOM side (a larger Stats Team project).
    Decision: the "Top locations" card is replaced by "Top videos by watch time", which reuses the same RankingCard and surfaces engagement rather than reach.

  2. Unique-visitor counts aren't recoverable. The video_stats table doesn't store viewer IDs or session keys, and stats/video-plays?complete_stats=true returns views, impressions, and watch_time per day — none of which is a unique-visitor count.
    Decision: the "Visitors" KPI is renamed to "Impressions". Impressions is real, returned by the same endpoint, and is a coherent reach metric distinct from views (which require a play).

The two-column bottom layout and three-card KPI row are preserved; only the metric in two slots changes.

Deferred follow-ups from this discovery (do not block Phase 7)

  • WPCOM-side: per-video country/region collection. Would unblock a real per-video "Top locations" card.
  • WPCOM-side: top-embed-pages aggregation over the page_url column already present in video_stats. Would surface a real geography-shaped insight without new collection.

Standalone investigations (pending external input)

  • Free-tier UX. VideoPress free tier allows a single upload (different shape from Activity Log's view-20 limit). The mocks don't include a free-tier state. Awaiting designer input. Once defined, expect to add an UpsellCallout analogous to Activity Log's, gated server-side in the REST proxy and UI-side via DataViews configuration.
  • Storage meter placement. VideoStorageMeter is not in the mocks. Map current display conditions in the legacy dashboard, then take to designers to decide on placement (Overview sidebar? Settings? Drop entirely?).
  • ConnectionError verification. During scoping, both trigger paths failed to render the component (PHP Error_Handler::report_error and JS wp.data.dispatch('jetpack-connection').setConnectionErrors). Likely a real bug in projects/js-packages/connection/hooks/use-connection-error-notice/. File a separate issue; ensure the new chrome renders the component once fixed.

Branch & PRs

Phase 0 landed in #48494 on branch update/videopress-modernization. Subsequent phases each open their own PR directly to trunk; the modernization filter keeps the in-progress dashboard hidden in production. Each phase's PR ticks the box above when merged.

Deferred follow-ups

  • phpunit + jest test scaffolding for the new package surfaces.
  • Analytics tracking events for new screens (parity with useAnalyticsTracks in the legacy app, plus new events for the Overview's KPI/chart interactions).

Out of scope

  • Calypso source porting — there is no Calypso source in this repo. The mocks and @automattic/charts are the design references; this is a from-scratch rebuild on Gutenberg primitives, not a port.
  • Multi-site / agency flows.
  • Net-new features beyond modernization parity (chapters editor UI, video transcripts, custom thumbnails beyond what exists today, etc.).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions