Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
735d0ca
Stats: split video details into a dedicated page modeled on post details
dognose24 Jul 10, 2026
3cbcffb
Stats: add period navigation to the video details chart
dognose24 Jul 10, 2026
6b711dc
Stats: add metric highlight tabs to the video details chart
dognose24 Jul 10, 2026
b171e3c
Stats: modernize the video details Embedded pages module
dognose24 Jul 10, 2026
59fe4ce
Stats: zero-fill the video details chart series
dognose24 Jul 10, 2026
db3e820
Stats: align the video details chart with the real stats/video semantics
dognose24 Jul 10, 2026
47187c6
Stats: trim the video details daily window to the trailing 30 days
dognose24 Jul 10, 2026
8f81611
Stats: keep video chart month labels on one line
dognose24 Jul 10, 2026
ed7f7f5
Stats: bucket video weekly stats Monday-based to match the date heading
dognose24 Jul 10, 2026
a71f21c
Stats: show full month-year video chart labels like post details
dognose24 Jul 10, 2026
b6cca06
Stats: stretch the video details header and embeds cards to full width
dognose24 Jul 10, 2026
6872b8d
Stats: track the video details screen in the stats navigation history
dognose24 Jul 10, 2026
5ad6257
Merge remote-tracking branch 'origin/trunk' into update/stats-299-vid…
dognose24 Jul 10, 2026
2a6dfa6
Stats: address review feedback on the video details page
dognose24 Jul 10, 2026
a70a3cf
Stats: degrade the video details page gracefully in Odyssey Stats
dognose24 Jul 10, 2026
ef2d3f8
Stats: drop the video details page's hard dependency on the media API
dognose24 Jul 10, 2026
6bc09e4
Stats: render video details media-derived UI only when the data exists
dognose24 Jul 10, 2026
693f12c
Stats: drop the thumbnail from the video details header card
dognose24 Jul 10, 2026
8d42f5d
Stats: source the video title and upload date from the statsVideo post
dognose24 Jul 10, 2026
bcba10c
Stats: polish the video details layout
dognose24 Jul 10, 2026
4bfb091
Stats: remove the video details retention rate card
dognose24 Jul 10, 2026
71241af
Stats: avoid flashing the video chart empty state when switching windows
dognose24 Jul 10, 2026
c535a2b
Stats: stop the video chart and metric cards from shifting layout
dognose24 Jul 10, 2026
3127e16
Stats: stack video metric cards one per row on small screens
dognose24 Jul 10, 2026
327fad0
Stats: pad the video metric cards off the viewport edges on narrow sc…
dognose24 Jul 10, 2026
4e2bb4c
Stats: truncate long breadcrumb labels with an ellipsis
dognose24 Jul 10, 2026
2b5612e
Stats: keep the header Jetpack logo from shrinking with long titles
dognose24 Jul 10, 2026
7488356
Stats: show exact zero and exact one hours watched on the video cards
dognose24 Jul 10, 2026
144ef4d
Stats: zero out the video details heading margin for wp-admin
dognose24 Jul 10, 2026
e1b4b45
Stats: restore the video details card loading placeholder
dognose24 Jul 10, 2026
6c99d8c
Stats: pin the embeds card heading margin for wp-admin
dognose24 Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions client/my-sites/stats/components/stats-main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function StatsBreadcrumbs( { items }: { items: BreadcrumbItem[] } ) {
<span className="stats-breadcrumbs__current">{ STATS_HEADER_TITLE }</span>
) }
{ restItems.map( ( item, index ) => (
<span key={ index }>
<span key={ index } className="stats-breadcrumbs__item">
<span className="stats-breadcrumbs__separator"> / </span>
{ item.to ? (
<a
Expand Down Expand Up @@ -138,7 +138,8 @@ export default function StatsMain( {
return (
<Main { ...props } className={ clsx( 'stats-main', 'color-scheme', customTheme, className ) }>
{ ! isWPAdminAndNotSimpleSite && <QuerySiteFeatures siteIds={ [ siteId ] } /> }
<QuerySiteSettings siteId={ siteId } />
{ /* The Odyssey stats-app proxy has no settings route, so the request would 404. */ }
{ ! isWPAdminAndNotSimpleSite && <QuerySiteSettings siteId={ siteId } /> }
<Page
// Restore a stable styling hook lost when @wordpress/admin-ui 2.x moved Page
// internals to CSS Modules. Stats SCSS overrides target `.admin-ui-page`.
Expand Down
22 changes: 19 additions & 3 deletions client/my-sites/stats/controller.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const loadStatsPostDetail = () =>
import(
/* webpackChunkName: "async-load-calypso-my-sites-stats-stats-post-detail" */ './stats-post-detail'
);
const loadStatsVideoDetail = () =>
import(
/* webpackChunkName: "async-load-calypso-my-sites-stats-stats-video-detail" */ './stats-video-detail'
);
const loadCommentFollows = () =>
import(
/* webpackChunkName: "async-load-calypso-my-sites-stats-comment-follows" */ './comment-follows'
Expand Down Expand Up @@ -335,8 +339,21 @@ export function summary( context, next ) {
);
const period = rangeOfPeriod( activeFilter.period, date );

const extraProps =
context.params.module === 'videodetails' ? { postId: parseInt( queryOptions.post, 10 ) } : {};
// Video details has its own dedicated page, modeled on the single post page.
if ( context.params.module === 'videodetails' ) {
context.primary = (
<StatsPageLoader>
<AsyncLoad
require={ loadStatsVideoDetail }
placeholder={ PageLoading }
postId={ parseInt( queryOptions.post, 10 ) }
period={ period }
context={ context }
/>
</StatsPageLoader>
);
return next();
}

// The option is used for stats queries only.
const statsQueryOptions = pick( queryOptions, [ 'num', 'summarize', 'geoMode', 'viewType' ] );
Expand All @@ -356,7 +373,6 @@ export function summary( context, next ) {
dateRange={ dateRange }
context={ context }
period={ period }
{ ...extraProps }
/>
</StatsPageLoader>
);
Expand Down
3 changes: 3 additions & 0 deletions client/my-sites/stats/hooks/use-stats-navigation-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const possibleBackLinks: { [ key: string ]: string | null } = {
postList: '{adminUrl}edit.php',
emailsummary: '/stats/{period}/emails/',
postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
videodetails: null, // Same as postDetails: tracked in the history stack, never rendered as a crumb.
};

const SUPPORTED_QUERY_PARAMS: string[] = [
Expand Down Expand Up @@ -158,6 +159,7 @@ export const useStatsNavigationHistory = (
postList: translate( 'Post List' ),
emailsummary: translate( 'Emails' ),
postDetails: null, // Last item in the history, the text is not displayed anywhere but this is used to track the item in history stack.
videodetails: null,
} ),
[]
);
Expand Down Expand Up @@ -288,6 +290,7 @@ export const useStatsBreadcrumbTrail = (
postList: translate( 'Post List' ),
emailsummary: translate( 'Emails' ),
postDetails: null,
videodetails: null,
} ),
[]
);
Expand Down
4 changes: 2 additions & 2 deletions client/my-sites/stats/stats-summary/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ class StatsSummaryChart extends Component {
label: tabLabel + label,
};

// The StatsPostSummary has been modernized to fresh styling.
const isModernized = 'post' === type;
// The post and video summaries have been modernized to fresh styling.
const isModernized = 'post' === type || 'video' === type;

return isModernized ? (
<div className={ clsx( 'is-summary-chart', { 'is-loading': isLoading } ) }>
Expand Down
120 changes: 120 additions & 0 deletions client/my-sites/stats/stats-video-detail/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { useTranslate } from 'i18n-calypso';
import { useEffect, useLayoutEffect } from 'react';
import titlecase from 'to-title-case';
import QueryMedia from 'calypso/components/data/query-media';
import Main from 'calypso/my-sites/stats/components/stats-main';
import {
useStatsBreadcrumbTrail,
recordCurrentScreen,
} from 'calypso/my-sites/stats/hooks/use-stats-navigation-history';
import { useSelector } from 'calypso/state';
import getMediaItem from 'calypso/state/selectors/get-media-item';
import { getSiteStatsNormalizedData } from 'calypso/state/stats/lists/selectors';
import { getSelectedSiteId } from 'calypso/state/ui/selectors';
import PageViewTracker from '../stats-page-view-tracker';
import VideoDetailsCard from './video-details-card';
import VideoEmbedsCard from './video-embeds-card';
import VideoSummary from './video-summary';

import './style.scss';

interface StatsVideoDetailProps {
postId: number;
period: {
period: string;
};
context: {
query: Record< string, string >;
};
}

interface VideoMediaItem {
title?: string;
date?: string;
/** Video duration in seconds. */
length?: number;
}

interface VideoStatsPost {
post_title?: string;
post_date?: string;
}

export default function StatsVideoDetail( { postId, period, context }: StatsVideoDetailProps ) {
const translate = useTranslate();
const siteId = useSelector( getSelectedSiteId );
// The video title and upload date come from the attachment post included in
// the statsVideo response — available in both Calypso and Odyssey (the
// stats-app proxy forwards stats routes). Mirrors VideoSummary's default
// Days/Weeks query, which is always fetched first.
const videoStatsData = useSelector(
( state ) =>
getSiteStatsNormalizedData( state, siteId, 'statsVideo', {
postId,
statType: 'views',
period: 'month',
} ) as { post?: VideoStatsPost | null } | null
);
const videoStatsPost = videoStatsData?.post ?? null;
// The media item is only needed for the video duration (retention rate);
// the request 404s harmlessly in Odyssey, where the stats-app proxy has no
// media route, and the retention card is simply omitted.
const media = useSelector(
( state ) => getMediaItem( state, siteId, postId ) as VideoMediaItem | null
);
const breadcrumbTrail = useStatsBreadcrumbTrail();
const statType = context.query.statType ?? null;

useEffect( () => {
window.scrollTo( 0, 0 );
}, [] );

// Must run before useStatsBreadcrumbTrail's passive effect reads the
// navigation history, so the trail treats this screen (not the previous
// one) as the current entry to exclude.
useLayoutEffect( () => {
recordCurrentScreen( 'videodetails', {
queryParams: context.query,
period: period.period,
} );
}, [ context.query, period.period ] );

const videoTitle = videoStatsPost?.post_title || media?.title || null;
const videoDate = videoStatsPost?.post_date || media?.date || null;
// Loading = neither source has responded yet; once statsVideo answers, a
// missing post means there is genuinely no title and the card hides.
const isVideoInfoLoading = ! videoStatsData && ! media;

return (
<Main
fullWidthLayout
breadcrumbs={ [
...breadcrumbTrail.map( ( item ) => ( {
label: item.label,
to: item.url ?? undefined,
} ) ),
{ label: videoTitle || translate( 'Video details', { textOnly: true } ) },
] }
>
<PageViewTracker
path={ `/stats/${ period.period }/videodetails/:site` }
title={ `Stats > ${ titlecase( period.period ) } > Videodetails` }
/>
{ siteId && <QueryMedia siteId={ siteId } mediaId={ postId } /> }
<div className="stats stats-summary-view">
<div
id="my-stats-content"
className="stats-summary-view stats-summary__positioned stats-video-detail"
>
<VideoDetailsCard
title={ videoTitle }
date={ videoDate }
isLoading={ isVideoInfoLoading }
/>
<VideoSummary postId={ postId } initialStatType={ statType } />
<VideoEmbedsCard postId={ postId } />
</div>
</div>
</Main>
);
}
Loading
Loading