Skip to content

Stats v2: Add thumbnails to the video lists - #53011

Merged
adamwoodnz merged 17 commits into
trunkfrom
stats-513-stats-v2-add-thumbnails-to-the-video-lists
Oct 7, 2026
Merged

adamwoodnz merged 17 commits into
trunkfrom
stats-513-stats-v2-add-thumbnails-to-the-video-lists

Conversation

@adamwoodnz

@adamwoodnz adamwoodnz commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes https://linear.app/a8c/issue/STATS-513

Why

In classic Stats, each video row starts with its thumbnail, so site owners can spot a video at a glance. Stats v2 showed no thumbnail in the VideoPress widget, and an unlinked one in the Videos report. Both lists now show the video's poster as a square, as the Stats v2 prototype does, and clicking it opens the video's detail page, like the title does. The detail page marks its poster with a play icon, also from the prototype.

Proposed changes

Builds on #52909 (merged), which moved the widget to the VideoPress package.

  • Top videos widget (packages/videopress): each row shows the video's poster as a 28 × 28px square before its title, inside the same link. A video with no poster, or whose poster fails to load, shows a bordered placeholder with the video icon, so titles stay aligned. The prototype's widget shows no thumbnails; we keep them on purpose.
  • Videos report: 32 × 32px posters, as in the Posts & Pages report, linked to the video detail page. The report passes isItemClickable and renderItemLink, so DataViews links the poster and the title itself. Rows without a video ID are not clickable, and their title keeps the external page link.
  • Video detail page: the 72 × 72px header poster gets the prototype's dark scrim and white video icon. A missing or broken poster still shows the plain video icon.
  • ReportRecordsTable: a @wordpress/ui Link zeroes border-radius on hover, which squared the report's linked poster. The table restores DataViews' radius on media links, for every report that uses renderItemLink.
  • Shared getVideoPosterUrl helper (widgets-toolkit): checks the URL with safeHttpUrl and asks Photon for the poster at twice its drawn size. The report uses it in place of its local copy, and the SDK exports it for the widget.
  • VideoTitleLink gains a children slot, and the toolkit exports InternalLink (from Premium Analytics: add shared internal and external link components to the widgets toolkit #53043) for the report's row link. Video leaderboard rows (videoLink) now render their media inside that link; before this change they dropped it.
  • Leaderboard thumbnail media accepts a fallbackIcon, drawn in place of the grey placeholder when there is no image or it fails to load. Thumbnails are held at 1:1, because the dashboard shell's img { height: auto } overrides a fixed height and would draw a landscape poster short.
  • Storybook: the video-plays mock gains posters, including one null poster (a private video) and one that fails to load.

Private videos: the stats API returns poster: null for them (STATS-412), so they show the placeholder without a failed request.

Screenshots

Local site with two videos: "Osaka at night" has a poster; the other's poster does not load, so it shows the placeholder.

Top videos widget (VideoPress package)

Top videos widget: a square video-icon placeholder and a square poster before each title

Videos report

Videos report rows with a square video-icon placeholder and a square poster

Video detail header

Video detail header: square poster with a play icon over a dark scrim

What a reviewer should check

  • Rows without a valid video ID: the report poster is not a link, and the widget row keeps its external or plain fallback with the poster inside.
  • Decisions made during the build that you may want to overrule:
    • Squares crop landscape posters to their centre. Nothing in the stats data says whether a video is landscape or portrait, so one shape serves both.
    • The scrim and icon on the detail poster use fixed colours (rgba(0, 0, 0, 0.28), white), not WPDS tokens: they sit on a photo, not a themed surface, and WPDS has no scrim token.
    • The widget placeholder's hairline uses --wpds-color-stroke-surface-neutral; DataViews' report placeholder hard-codes rgba(0, 0, 0, 0.1), a shade lighter.
    • The positive-integer video ID check is repeated in three places: the report's isVideoRowClickable, VideoTitleLink, and the widget row builder. Merging them is left for a follow-up.
    • Storybook mock posters load from picsum.photos. They are story-only and show placeholders when offline.

Verification

  • Jest passes in Premium Analytics (all 3932 tests) and in VideoPress (widgets/top-videos). Typecheck, ESLint and stylelint pass, and both packages build.
  • Local site on real Stats data, one video with a poster:
    • Videos report: the poster is 32 × 32px with a 4px radius, which now holds on hover and keyboard focus; the focus ring follows it. Clicking the poster opens /video/<id>.
    • Video detail page: the poster is 72 × 72px, and the scrim and icon cover it with the icon centred. Post detail pages keep their square header unchanged.
    • Dashboard widget: the poster draws 28 × 28px, requested from Photon at 56 × 56px, and the video without a poster shows the 28 × 28px placeholder.
  • Not verified: a truly private video. The test site's "private" video is set to the site default, which is public, so its poster loads.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  • On a site with VideoPress videos that have plays in the selected range, open Stats v2:
    • Dashboard › Top videos widget: square posters show before titles, and clicking one opens the video detail page. Titles stay aligned, and tabbing through the list stops once per row.
    • Videos report (/reports/videos): posters are 32 × 32px squares and keep their rounded corners on hover. Clicking a poster opens the detail page with the same date range as the title. The poster and the title are separate tab stops, and the poster link is named after the title.
    • Video detail page: the header poster shows the video icon over a dark scrim.
    • A private video shows the placeholder.
    • Posts & Pages report: thumbnails and placeholders are unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UZjr8dPvykFAD46sUK54zL

@adamwoodnz adamwoodnz added Enhancement Changes to an existing feature — removing, adding, or changing parts of it [Package] Premium Analytics [Plugin] Premium Analytics labels Oct 1, 2026
@adamwoodnz adamwoodnz self-assigned this Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the stats-513-stats-v2-add-thumbnails-to-the-video-lists branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack stats-513-stats-v2-add-thumbnails-to-the-video-lists

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions Bot added [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ Docs labels Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

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:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Jetpack plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Videopress plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Mu Wpcom plugin:

  • Next scheduled release: WordPress.com Simple releases happen semi-continuously (PCYsg-Jjm-p2)

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Wpcomsh plugin:

  • Next scheduled release: Atomic deploys happen twice daily on weekdays (p9o2xV-2EN-p2)

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Premium Analytics plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@adamwoodnz

Copy link
Copy Markdown
Contributor Author

Codex review

No correctness, security, accessibility, or contract issues were identified in the branch changes.

@adamwoodnz

Copy link
Copy Markdown
Contributor Author

@claude please review this PR. Get the change set with a single gh pr diff 53011, then read only the changed source files it touches. Skip changelog entries and lockfiles. Use at most about 20 tool calls, and post your review before you reach that, even if it is incomplete.

@claude

claude Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @adamwoodnz's task in 1m 13s —— View job


Review of PR #53011: video poster thumbnails

  • Fetch the diff (gh pr diff 53011)
  • Read the changed source files (changelog entries skipped)
  • Post review

Verdict: I found no blocking issues. The change is small and well tested, and the poster markup is accessible. Below are a few things I checked and some optional nits.

What I checked

  • URL safety: getVideoPosterUrl (packages/widgets-toolkit/src/helpers/video-plays.ts:43) runs safeHttpUrl before calling new URL(). A bad or non-string poster returns undefined and never throws. The tests cover javascript:, null, '' and numbers. An existing query string is kept.

  • Report poster link (routes/reports/videos/config/fields.tsx:62): tabIndex={-1} together with aria-hidden is valid. Because the link can't take keyboard focus, it doesn't break axe's aria-hidden-focus rule. The Videos page sets no isItemClickable or renderItemLink, so DataViews won't wrap the media cell in a second link and you don't get a link inside a link. Passing getVideoDetailSearch as the search function matches how VideoTitle already does it, so the poster and the title carry the same date range.

  • LeaderboardRow videoLink branch (leaderboard-row.tsx:127): all three fallbacks match the non-title-link path.

    • Internal links and external links both wrap LeaderboardLabel with decorativeMedia.
    • Plain rows use a styles.row span.

    The accessible name stays the title, because the image has alt="". The only other videoLink caller, the WithMediaAndLinks story, passes no media, so turning media on for videoLink changes nothing anywhere else.

  • VideoDetailLink extraction: this is a clean refactor. VideoTitleLink's output is unchanged when no children are passed.

  • Changelog entries: there are entries for the package, plugins/premium-analytics and plugins/jetpack (with the Premium Analytics: prefix). That fits the AGENTS.md rules, since Jetpack users see Stats v2.

Nits and suggestions (optional)

  1. ReportThumbnail placeholder sizing (report-thumbnail.module.scss:4-7): inline-size: 100% and block-size: 100% only work because DataViews gives the media cell a definite size. Both current callers, Posts and Videos, render inside that cell, so this is fine today. The component README still describes it as a "records-table row thumbnail", so it may help to add a line saying it fills its media box.
  2. README table (chart-leaderboard/README.md:215): the new thumbnail row is much wider than the others, so the column pipes no longer line up. Either move the 16:9 detail into a sentence under the table, or shorten the cell to something like 28 × 28px (16:9: 50 × 28px).
  3. Photon resize on any host: getVideoPosterUrl adds ?resize= to whatever host the poster comes from. That's harmless: i0.wp.com and *.files.wordpress.com honour it, and other hosts ignore it. The JSDoc says "resized by Photon", though, so "resized via Photon-style resize param" would be more accurate if posters can come from elsewhere.
  4. Positive-integer ID check now in three places: the report poster, VideoTitleLink and the widget row builder. You already flagged this as a follow-up. A small toVideoId( id ): number | undefined helper next to getVideoPosterUrl would be a natural home for it.
  5. Thumbnail fit: .thumbnailWide uses object-fit: cover, so a non-16:9 poster (e.g. a vertical video) gets centre-cropped. That's reasonable at this size; I'm only mentioning it so it's a deliberate choice.

Not verified

  • The repo's node_modules aren't installed in this checkout, so I couldn't confirm against the installed @wordpress/dataviews types that the table layout honours layout.aspectRatio. The PR description says it was checked in the browser (57 × 32 box), and page.test.tsx checks that the setting is passed through.
  • I didn't run Jest or the linters. This was a read-only review kept to the requested tool budget.

@jp-launch-control

jp-launch-control Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 2 files.

File Coverage Δ% Δ Uncovered
projects/packages/premium-analytics/packages/widgets-toolkit/src/components/chart-leaderboard/leaderboard-row.tsx 19/19 (100.00%) 0.00% 0 💚
projects/packages/premium-analytics/packages/widgets-toolkit/src/components/chart-leaderboard/leaderboard-label.tsx 16/16 (100.00%) 14.29% -2 💚

1 file is newly checked for coverage.

File Coverage
projects/packages/premium-analytics/packages/widgets-toolkit/src/helpers/video-poster-url.ts 6/6 (100.00%) 💚

Full summary · PHP report · JS report

@adamwoodnz

Copy link
Copy Markdown
Contributor Author

Responses to Claude's nits:

  1. ReportThumbnail placeholder: not changed. Its JSDoc ("A records-table row thumbnail, rendered by the view's mediaField") already ties it to the DataViews media cell, and both callers render there.
  2. README table: fixed in e362438. The cell is now 28 × 28px (16:9: 50 × 28), and the sizing note sits in a sentence under the table.
  3. Photon wording: not changed. Posters come from the WordPress.com video CDN or Photon, and both honour resize.
  4. Duplicated video ID check: left for a follow-up, as the PR description notes.
  5. object-fit: cover: deliberate. A poster in a non-16:9 shape is centre-cropped to fill the slot, so titles stay aligned.

@adamwoodnz adamwoodnz added [Status] Needs Review This PR is ready for review. and removed [Status] In Progress labels Oct 1, 2026
@adamwoodnz
adamwoodnz marked this pull request as ready for review October 1, 2026 01:47
@adamwoodnz
adamwoodnz requested a review from a team as a code owner October 1, 2026 01:47
@adamwoodnz

Copy link
Copy Markdown
Contributor Author

Review cycle summary

  • Round reviewer: Codex, 1 round. It found no issues, so the round phase ended early.
  • Final reviewer: Claude, 1 round. No blocking issues and 5 optional nits:
    • Fixed: the README table alignment (e362438).
    • Answered with reasons: the other 4.
  • CI: all checks passed on c565d2c. The README-only push e362438 started a new run.
  • Inline review threads: none.
  • Unaddressed human comments: none.

Status: clean. Ready for human review.

@Nikschavan Nikschavan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I left a few observations inline. None of them block this.

What I checked: I loaded the VideoPress widget and the Videos report from this branch on a site with real VideoPress posters. In the widget, the posters render at 50×28 inside each row link and open the video detail page. In the report, the poster boxes are 57×32 and the poster link stays out of the tab order. In Storybook, the null poster and the broken poster both fall back to the placeholder, and each row stays 36px tall.

VideoPress widget Videos report
Widget: 16:9 posters with placeholders for the null and broken posters Videos report: 16:9 posters on real data

Comment thread projects/packages/premium-analytics/routes/reports/videos/page.tsx Outdated

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for adding the posters! @adamwoodnz Left a few inline notes, mainly on the report poster click.

Comment thread projects/packages/premium-analytics/routes/reports/videos/config/fields.tsx Outdated
@adamwoodnz
adamwoodnz force-pushed the stats-513-stats-v2-add-thumbnails-to-the-video-lists branch from c6a47e8 to aae49d2 Compare October 1, 2026 21:22
@adamwoodnz
adamwoodnz requested a review from a team as a code owner October 1, 2026 21:22
@adamwoodnz
adamwoodnz changed the base branch from trunk to update/pa-extensibility-videopress-top-videos October 1, 2026 21:22
Base automatically changed from update/pa-extensibility-videopress-top-videos to trunk October 2, 2026 09:23
@github-actions github-actions Bot added [Plugin] mu wpcom jetpack-mu-wpcom plugin [Plugin] VideoPress A standalone plugin to add high-quality VideoPress videos to your site. [Plugin] Wpcomsh labels Oct 2, 2026
@adamwoodnz
adamwoodnz force-pushed the stats-513-stats-v2-add-thumbnails-to-the-video-lists branch 3 times, most recently from 56fe1e0 to 22177e3 Compare October 6, 2026 22:09
adamwoodnz and others added 17 commits October 7, 2026 15:13
Add getVideoPosterUrl() to resolve safe video poster URLs resized by Photon.
Later tasks call this from the VideoPress widget and the Videos report.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
Video rows show a poster that opens the same detail page as the title. Extract VideoDetailLink so the Videos report can reuse the link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
Video rows now carry a resized, safety-checked poster as a 16:9 thumbnail inside the row link, with a placeholder when a video has none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
Video posters now render at 16:9 and link to the video detail page. The thumbnail placeholder fills the media box so it stays centred in both square and 16:9 tables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
Gives the mock the poster shapes the real API returns (URL, null for private, a URL that fails to load) and pins the default plays shape in the data layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
Corrects the leaderboard README, moves the videos fields styles to a plain CSS module so the build passes, and makes the poster tests exercise the real code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
The Stats v2 dashboard ships in both plugins, so their users read about the change there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
…533)

The 16:9 thumbnail note made the table row too wide to line up; it now sits in a sentence under the table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
A grey box gave no hint of what was missing, and the Videos report already draws the video icon there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
… from VideoPress

The Top videos widget moved to the VideoPress package, so it gets its own entry, the SDK declares the helper, and the Premium Analytics entry keeps only what that package ships.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
The hand-built poster link sat under DataViews' media overlay, so clicking the poster did nothing. DataViews' renderItemLink wraps the poster and the title itself, the same way for every report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
With media 'none' the test never reached the video-only branch, so dropping the videoLink check still passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
CodeQL's dynamic workflow cannot be retried on an existing commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECZA3cSHSTyAFqwR9JXUU7
Trunk now asks each test to name a bug no other test catches. Drop the
default-shape poster normalizer test (same parse path as the
complete-stats one) and the poster-has-no-link test (no branch can add
one). Stop re-testing safeHttpUrl's rejections in getVideoPosterUrl.

Move the external-link children check down to VideoTitleLink, covering
all three branches, and assert that the Videos page wires DataViews'
row link: dropping renderItemLink left every test green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZjr8dPvykFAD46sUK54zL
At 50 × 28 the video poster looked heavy beside the flags and icons in
the other leaderboards. Draw it at a flag's 21px height instead, and
request the Top Videos poster at the new 2x size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZjr8dPvykFAD46sUK54zL
The header's square box cropped a video poster to its middle, and the
leaderboard and Videos report now show the same poster at 16:9. Let
SectionHeader widen its visual to 16:9 at the square's height, and use
it on the video detail page in every state so the box keeps its shape
while the video loads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZjr8dPvykFAD46sUK54zL
The Stats v2 prototype shows video thumbnails as squares, matching the
Posts & Pages report, and marks the detail page's poster with a play
icon. Drop the 16:9 boxes everywhere, add the icon overlay to the
detail header, and request posters at twice the square sizes.

A @wordpress/ui Link zeroes its border radius on hover, which squared
the report's linked poster; the records table now restores DataViews'
radius on its media links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UZjr8dPvykFAD46sUK54zL
@adamwoodnz
adamwoodnz force-pushed the stats-513-stats-v2-add-thumbnails-to-the-video-lists branch from 9b3bf8e to 7d7526d Compare October 7, 2026 02:14
@adamwoodnz
adamwoodnz merged commit a93dc9a into trunk Oct 7, 2026
83 checks passed
@adamwoodnz
adamwoodnz deleted the stats-513-stats-v2-add-thumbnails-to-the-video-lists branch October 7, 2026 02:35
@github-actions github-actions Bot added [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. and removed [Status] In Progress [Status] Needs Review This PR is ready for review. labels Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Docs Enhancement Changes to an existing feature — removing, adding, or changing parts of it [JS Package] Premium Analytics Sdk [mu wpcom Feature] Premium Analytics [mu wpcom Feature] Wpcom Admin Menu [mu wpcom Feature] Wpcom Videopress [Package] Jetpack mu wpcom WordPress.com Features [Package] Premium Analytics [Package] VideoPress [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Plugin] mu wpcom jetpack-mu-wpcom plugin [Plugin] Premium Analytics [Plugin] VideoPress A standalone plugin to add high-quality VideoPress videos to your site. [Plugin] Wpcomsh RNA [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants