VideoPress: make the modernized dashboard fully functional on WordPress.com Simple#50410
VideoPress: make the modernized dashboard fully functional on WordPress.com Simple#50410vianasw wants to merge 11 commits into
Conversation
…sites Menu + boot (VIDP-281), mirroring how Newsletter is wired on Simple. - Admin_UI::init() is now host-aware: on WordPress.com Simple it skips the standalone Admin_Menu path and loads only the wp-build dashboard assets at admin_menu:1 (new maybe_load_wp_build()); the new add_wp_admin_submenu() is called from wpcom-admin-menu.php at priority 999999 once the Jetpack parent menu exists. The standalone/Atomic path is unchanged. - New jetpack-mu-wpcom "wpcom-videopress" feature boots Admin_UI + Initial_State on Simple only (class_exists-guarded). - Initial_State emits siteData.isSimple; the library item mapping (use-library/use-video) branches on it because Simple's /wp/v2/media omits media_details.videopress — poster/duration come from media_details.{thumb,duration_milliseconds} and the VideoPress type is keyed off jetpack_videopress.guid. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
Code Coverage SummaryCoverage changed in 14 files. Only the first 5 are listed here.
3 files are newly checked for coverage.
Full summary · PHP report · JS report Coverage check overridden by
Coverage tests to be added later
|
…mple The dashboard boots on Simple but rendered the connection/pricing gate: Simple sites have no Jetpack site/user connection, and the plan-access check didn't resolve there. - ConnectionGate: bypass on Simple (isSimpleSite) — Simple sites are inherently wpcom-connected; access is already gated by the VideoPress site feature that surfaces the menu. useConnection still runs, so hook order stays stable. - Initial_State::has_videopress_access(): on Simple use wpcom_site_has_feature( 'videopress' ) (the slug Admin_UI already uses) instead of the storage-tier product features, which don't resolve on Simple. - Initial_State: skip the pricing payload on Simple (the gate is bypassed there). Co-Authored-By: Claude Opus 4.8 <[email protected]>
On WordPress.com Simple, `media_details.thumb` is a bare filename, not a URL, so library posters rendered broken. Construct the VideoPress CDN poster URL from the video guid + thumb filename (the same URL shape the editor already uses for chapter/thumbnail files), falling back to the self-hosted `media_details.videopress.poster`. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…m Simple Makes the dashboard fully functional on Simple (VIDP-282 + VIDP-283). Library (VIDP-283): - Video-only filtering: new videopress_only_videos query param handled by an IS_WPCOM branch in the attachment Data class (post_mime_type = 'video', exact X-WP-Total), since mime_type=video/* doesn't narrow on public-api. The query filter now registers unconditionally (no-op without the param off-Simple). - Privacy/type filters run client-side on Simple (bounded over-fetch) since the postmeta-backed server filters don't apply (data lives in wpcom's videos table). Panels (VIDP-282): - Stats: reroute the Overview to native /rest/v1.1/stats/video-plays on Simple (the wpcom apiFetch middleware handles that namespace; same response shape). - Settings: new wpcom/v2/videopress/settings GET/POST routes (videopress/v1 can't register on Simple); frontend reroutes there on Simple. POST only persists auto-subtitles on wpcom (private-for-site is whole-site there). - Storage + features queries are disabled on Simple (storage reads 0; features come from the boot payload); Initial_State reads feature flags via wpcom_site_has_feature on Simple, avoiding a blocking remote request. All new behavior is isSimpleSite()/IS_WPCOM-gated; self-hosted/Atomic unchanged. 183 JS tests + 27 PHP tests pass. Co-Authored-By: Claude Opus 4.8 <[email protected]>
On Simple, VideoPress privacy follows the whole-site Privacy setting — there's no independent per-VideoPress toggle and the write is a no-op — so render that control read-only with explanatory help instead of a toggle that appears to do nothing. The auto-subtitles setting stays editable (it maps to a real option). Co-Authored-By: Claude Opus 4.8 <[email protected]>
…imple On Simple isProcessing was hard-coded false, so VideoPress attachments that have a guid but empty media_details (unprocessed/orphaned records — no poster, no duration, video won't load) rendered as bare black tiles instead of a processing placeholder. Drop the Simple guard so a poster-less VideoPress item reports isProcessing, matching self-hosted (the finished flag is absent on Simple, so the poster check carries it). Co-Authored-By: Claude Opus 4.8 <[email protected]>
The @wordpress/ui Dialog overlay reads its z-index from --wp-ui-dialog-z-index (default `auto`), which loses the stacking contest to WordPress.com's wide, high-z admin menu: the fixed backdrop renders behind the sidebar, so the menu stays bright and the centered dialog looks crammed against the content area. The @wordpress/components Modal (subtitle manager) already clears this at its 100000 overlay layer. Set --wp-ui-dialog-z-index: 100000 on the dashboard body so every Dialog (thumbnail picker, chapters help) dims the full viewport and centers over it, matching the subtitle manager — identically on self-hosted and Simple, with no per-environment sidebar-width math. The Dialog portals into <body>, which carries the page class, so the property inherits into the portaled backdrop and popup. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Setting a video poster POSTs to the WPCOM `videos/{guid}/poster` endpoint,
authorized with a one-time upload token. On Simple that token can't be minted
server-side: `VideoPressToken::videopress_onetime_upload_token()` calls the
classic `media/token` (rest/v1.1) endpoint, and the in-process transport that
`wpcom_json_api_request_as_blog` falls back to on Simple (`WPCOM_API_Direct`)
only dispatches WP-REST routes — so the mint fails and the save 500s with
"Could not obtain a VideoPress upload token."
The frontend can reach the site-scoped `media/token` via the wpcom apiFetch
proxy with the user's own auth, so on Simple it mints the token there and passes
it to our poster endpoint, which uses it in place of the server-side mint. It's
a blog-scoped, one-time token the user could mint anyway, and the route already
requires `upload_files`, so nothing extra is granted. The token is an md5 hash;
the backend keeps only hex before placing it in the `token="…"` Authorization
header so a crafted value can't break out of it. Self-hosted is unchanged (no
token is passed, so it mints as before).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Publishing or deleting a subtitle track uploads to the WPCOM
`videos/{guid}/tracks` endpoint, which authorizes on either
`current_user_can('upload_files')` or a one-time upload token. On the
modernized dashboard on Simple, the track functions took the proxied
apiFetch/global path, which reaches the endpoint without upload
authorization — so it 403s with "You do not have permission to upload
files." The self-hosted path (mint token -> direct public-api fetch with
X_UPLOAD_TOKEN) is what's needed, but `getMediaToken('upload')` can't mint
on Simple: its admin-ajax handler reaches the classic `media/token`
endpoint in-process, which isn't dispatchable there.
On the Simple dashboard, mint the token via the site-scoped `media/token`
proxy (the user's auth reaches it, and it returns a blog-scoped token the
tracks endpoint accepts) and take the same direct public-api fetch. Verified
on the sandbox: a no-op delete with the proxy-minted token returns 404 (past
auth), not 403 — CORS and token auth both work. Self-hosted/Atomic and
Calypso paths are unchanged.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Cleanup after the poster and subtitle-track Simple fixes: both inlined the
same `POST /rest/v1.1/media/token` proxy call with slightly different (and
inconsistent) response typings. Extract it once as `mintSimpleUploadToken()`
in the designated token module (client/lib/get-media-token) — returning the
same `{ token, blogId }` shape getMediaToken uses — and call it from both the
poster hook and the tracks lib. Also drop the redundant `window as unknown`
cast in `isSimpleDashboard()` (JPVIDEOPRESS_INITIAL_STATE is an ambient global
declared package-wide, so it reads directly like isSimpleSite()) and collapse
a now-needless ternary. No behavior change.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Code reviewFound 1 issue:
jetpack/projects/packages/videopress/src/dashboard/hooks/use-library.ts Lines 379 to 400 in df6951a 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
On Simple, getClientSideFilters treated a `type: videopress` filter as client-side, so fetchLibrary took the bounded over-fetch path for it. But viewToQueryArgs always sends `videopress_only_videos`, which already restricts the server query to VideoPress videos — so that filter is a no-op there. The cost fell on useFreeTier's COUNT_VIEW (deliberately `perPage: 1` with a `type: videopress` filter to read an exact X-WP-Total cheaply): on every Library/Overview load it instead fetched up to CLIENT_FILTER_MAX_PAGES × CLIENT_FILTER_PER_PAGE rows and capped totalItems at that window, under-counting large libraries. Only surface `local` as a client-side type filter; `videopress` is left to the server. COUNT_VIEW now stays on the single-request path (honors perPage, reads the exact total), and the VideoPress-type library view no longer over-fetches or caps at the window. Adds a getClientSideFilters unit test and a single-request regression test. Found in the #50410 code review. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
Addressed in Root cause: jetpack/projects/packages/videopress/src/dashboard/hooks/use-library.ts Lines 210 to 224 in 2c94eac |
obenland
left a comment
There was a problem hiding this comment.
We were probably both hoping this would be easier, but I'm glad to see that it's possible (and not toooo crazy).
Overall I think it would be worthwhile doing a couple more simplification iterations to find every opportunity where we can avoid a simple site check. The rest api namespace changes would be a good one, for example.
I ran the PR through a simplification-focussed AI review and saved the results in https://gist.github.com/obenland/b5882b1ec3e97b7482cc4f80d458556b. It found a few opportunities that might be worth feeding back into your agents.
| help={ | ||
| simple | ||
| ? __( | ||
| 'On WordPress.com this follows your site’s Privacy setting.', |
There was a problem hiding this comment.
WordPress.com users don't differentiate between WordPress.com and other environments. Why does this help text need to be changed for Simple Sites in the first place?
| staleTime: 5 * 60_000, | ||
| // The videopress/v1 namespace never reaches the REST dispatcher on | ||
| // WordPress.com Simple; the boot payload (`siteData.hasVideoPressAccess` | ||
| // / `isVideoPress1TB` / `isVideoPressUnlimited`) covers the flags there. |
There was a problem hiding this comment.
Looks like another reason to work on the WPCOM_Features stuff.
The videopress/v1 namespace never reaches the REST dispatcher
Should it? As in, should this Rest API endpoint work? If yes, consider moving it to the wpcom/v2 namespace so it does.
| * @return The REST path for reading/writing VideoPress settings. | ||
| */ | ||
| function settingsPath(): string { | ||
| return isSimpleSite() ? '/wpcom/v2/videopress/settings' : '/videopress/v1/settings'; |
There was a problem hiding this comment.
Maybe this should just be moved to wpcom/v2 to simplify
| // On WordPress.com Simple the jetpack/v4 proxy doesn't exist; apiFetch's | ||
| // rest/v1.1 middleware site-prefixes this path and unwraps the envelope, | ||
| // landing on the same WPCOM endpoint the proxy targets. | ||
| const SIMPLE_REST_PATH = '/rest/v1.1/stats/video-plays'; |
There was a problem hiding this comment.
What makes the v4 proxy necessary and how could this be simplified?
| * | ||
| * @return `true` on WordPress.com Simple, `false` everywhere else. | ||
| */ | ||
| export function isSimpleSite(): boolean { |
There was a problem hiding this comment.
There's no environment helper for this?
| 'pricing' => ( new Connection_Manager() )->is_connected() ? null : $this->get_pricing_data(), | ||
| // makes. Skipped on WordPress.com Simple, where the connection gate is | ||
| // bypassed (Simple sites are inherently wpcom-connected). | ||
| 'pricing' => ( ( new Host() )->is_wpcom_simple() || ( new Connection_Manager() )->is_connected() ) ? null : $this->get_pricing_data(), |
There was a problem hiding this comment.
Bummer that ( new Connection_Manager() )->is_connected() doesn't handle that out of the box.
| // On WordPress.com Simple, access is governed by the site's plan feature — | ||
| // the same gate that surfaces the dashboard menu — not the storage-tier | ||
| // product features used off-platform (which don't resolve on Simple). Uses | ||
| // the bare 'videopress' slug, matching Admin_UI's existing feature check. |
There was a problem hiding this comment.
| // On WordPress.com Simple, access is governed by the site's plan feature — | |
| // the same gate that surfaces the dashboard menu — not the storage-tier | |
| // product features used off-platform (which don't resolve on Simple). Uses | |
| // the bare 'videopress' slug, matching Admin_UI's existing feature check. |
| // On WordPress.com Simple the features API is local — ask it directly | ||
| // (same slugs: the storage tiers are first-class WPCOM features) | ||
| // instead of issuing the blocking remote request below on every | ||
| // dashboard render. The function_exists guard keeps this safe | ||
| // mid-deploy and off-platform. |
There was a problem hiding this comment.
| // On WordPress.com Simple the features API is local — ask it directly | |
| // (same slugs: the storage tiers are first-class WPCOM features) | |
| // instead of issuing the blocking remote request below on every | |
| // dashboard render. The function_exists guard keeps this safe | |
| // mid-deploy and off-platform. |
| // Registered unconditionally: on WPCOM the filter handles the | ||
| // `videopress_only_videos` param (mime-based, see below); everywhere | ||
| // else it handles the meta-query-backed VideoPress filters. |
There was a problem hiding this comment.
| // Registered unconditionally: on WPCOM the filter handles the | |
| // `videopress_only_videos` param (mime-based, see below); everywhere | |
| // else it handles the meta-query-backed VideoPress filters. |
Proposed changes
Makes the modernized "Jetpack › VideoPress" dashboard fully functional on WordPress.com Simple sites, the way it already works on self-hosted Jetpack and Atomic. All new behavior is gated on
isSimpleSite()(JS) /Host::is_wpcom_simple()/IS_WPCOM(PHP), so self-hosted and Atomic are unchanged.Verified end-to-end on a Simple sandbox: menu → boot → library (filtered, with posters + durations) → Overview stats → Settings → video detail/edit → upload.
Menu + boot
Admin_UIis host-aware on Simple: skips the standaloneAdmin_Menupath, loads only the wp-build assets atadmin_menu:1, and a newadd_wp_admin_submenu()is called fromwpcom-admin-menu.php(999999). Newjetpack-mu-wpcomwpcom-videopressfeature bootsAdmin_UI+Initial_Stateon Simple.Initial_StateemitssiteData.isSimple; theConnectionGateis bypassed on Simple (inherently wpcom-connected);has_videopress_access()useswpcom_site_has_feature('videopress').Library
media_details.videopressis absent on Simple → poster from the VideoPress CDN{guid}/{thumb}, duration fromduration_milliseconds).videopress_only_videosquery param (anIS_WPCOMbranch in the attachment Data class →post_mime_type LIKE 'video/%', exact totals), sincemime_type=video/*doesn't narrow on public-api. Privacy/type filters run client-side on Simple (bounded over-fetch).Panels
/rest/v1.1/stats/video-playson Simple.wpcom/v2/videopress/settingsGET/POST routes (thewpcom/v2namespace is the only one that reaches Simple's REST dispatcher;videopress/v1cannot). Auto-subtitles is editable; the "private for site" control is read-only on Simple (privacy is whole-site there).Related product discussion/links
Does this pull request change what data or activity we track or use?
No.
Testing instructions
Requires a WordPress.com Simple site (sandbox). On self-hosted Jetpack / Atomic there should be no change.
bin/jetpack-downloader test jetpack add/videopress-dashboard-on-simpleand… jetpack-mu-wpcom-plugin add/videopress-dashboard-on-simple(VideoPress must be enabled on the site).