Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
531b9b4
Premium Analytics: add file-downloads widget
dognose24 Jun 29, 2026
7bf7dc9
Premium Analytics: changelog for file-downloads widget
dognose24 Jun 29, 2026
df174bf
Fix CI: Storybook tests — wrap LeaderboardChart stories in GlobalChar…
dognose24 Jun 29, 2026
f78ec5a
Address review: fix category to stats, document no-comparison intent
dognose24 Jun 29, 2026
b0db0ab
Premium Analytics: show file downloads comparison deltas
dognose24 Jun 29, 2026
2c14e22
Premium Analytics: align file downloads widget feedback
dognose24 Jun 30, 2026
3e46cae
Premium Analytics: Align file downloads widget stories
dognose24 Jun 30, 2026
a8a63eb
Add File Downloads to default dashboard
dognose24 Jul 1, 2026
c37fab0
Handle File Downloads Jetpack site errors
dognose24 Jul 1, 2026
23b1e97
Detect File Downloads unavailable errors
dognose24 Jul 1, 2026
73c66d4
Wrap File Downloads states with content padding
dognose24 Jul 1, 2026
5ab6c80
Center File Downloads placeholder states
dognose24 Jul 1, 2026
3c7f562
Merge remote-tracking branch 'origin/trunk' into add/wooa7s-1498-port…
dognose24 Jul 1, 2026
b2ccd7e
Premium Analytics: test file downloads dashboard layout
dognose24 Jul 1, 2026
7bf4a5b
Premium Analytics: fix stats mock lint
dognose24 Jul 1, 2026
b44b98f
Keep File Downloads header across states
dognose24 Jul 1, 2026
cd03921
Hide File Downloads title in close-up stories
dognose24 Jul 1, 2026
403e9eb
Use leaderboard empty state for unsupported file downloads
dognose24 Jul 1, 2026
143d6ec
Keep File Downloads errors on custom surface
dognose24 Jul 1, 2026
d4187fd
Add icon to File Downloads widget header
dognose24 Jul 1, 2026
27a6e67
Merge remote-tracking branch 'origin/trunk' into add/wooa7s-1498-port…
dognose24 Jul 2, 2026
e6a2181
Merge remote-tracking branch 'origin/trunk' into add/wooa7s-1498-port…
dognose24 Jul 2, 2026
b2ff78d
Guard File Downloads comparison rows
dognose24 Jul 2, 2026
d9f6bc6
Fix File Downloads comparison formatting
dognose24 Jul 2, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add file-downloads widget porting the Jetpack Stats File downloads module into Premium Analytics.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,106 @@ import type { APIFetchMiddleware, APIFetchOptions } from '@wordpress/api-fetch';
const STATS_BASE = '/jetpack-premium-analytics/v1/proxy/v1.1/stats';
const DAY_IN_MS = 24 * 60 * 60 * 1000;

const MOCK_FILE_DOWNLOADS_FILES = [
{
relative_url: '/annual-report-2025.pdf',
filename: 'annual-report-2025.pdf',
download_url: 'https://example.com/annual-report-2025.pdf',
downloads: '3840',
},
{
relative_url: '/product-brochure.pdf',
filename: 'product-brochure.pdf',
download_url: 'https://example.com/product-brochure.pdf',
downloads: '2610',
},
{
relative_url: '/getting-started-guide.pdf',
filename: 'getting-started-guide.pdf',
download_url: 'https://example.com/getting-started-guide.pdf',
downloads: '1920',
},
{
relative_url: '/press-release-q1.docx',
filename: 'press-release-q1.docx',
download_url: 'https://example.com/press-release-q1.docx',
downloads: '1305',
},
{
relative_url: '/logo-assets.zip',
filename: 'logo-assets.zip',
download_url: 'https://example.com/logo-assets.zip',
downloads: '870',
},
];

const MOCK_FILE_DOWNLOADS_COMPARISON_FILES = [
{
relative_url: '/annual-report-2025.pdf',
filename: 'annual-report-2025.pdf',
download_url: 'https://example.com/annual-report-2025.pdf',
downloads: '3200',
},
{
relative_url: '/product-brochure.pdf',
filename: 'product-brochure.pdf',
download_url: 'https://example.com/product-brochure.pdf',
downloads: '2900',
},
{
relative_url: '/getting-started-guide.pdf',
filename: 'getting-started-guide.pdf',
download_url: 'https://example.com/getting-started-guide.pdf',
downloads: '1600',
},
{
relative_url: '/press-release-q1.docx',
filename: 'press-release-q1.docx',
download_url: 'https://example.com/press-release-q1.docx',
downloads: '1500',
},
{
relative_url: '/logo-assets.zip',
filename: 'logo-assets.zip',
download_url: 'https://example.com/logo-assets.zip',
downloads: '700',
},
];

const MOCK_FILE_DOWNLOADS = {
date: '2026-06-29',
period: 'day',
days: {
'2026-06-29': {
files: MOCK_FILE_DOWNLOADS_FILES,
other_downloads: 0,
total_downloads: 10545,
},
},
summary: {
files: MOCK_FILE_DOWNLOADS_FILES,
other_downloads: 0,
total_downloads: 10545,
},
};

const MOCK_FILE_DOWNLOADS_COMPARISON = {
date: '2026-05-30',
period: 'day',
days: {
'2026-05-30': {
files: MOCK_FILE_DOWNLOADS_COMPARISON_FILES,
other_downloads: 0,
total_downloads: 9900,
},
},
summary: {
files: MOCK_FILE_DOWNLOADS_COMPARISON_FILES,
other_downloads: 0,
total_downloads: 9900,
},
};

type UtmMock = {
top_utm_values: Record< string, number >;
top_posts: Record< string, unknown[] >;
Expand Down Expand Up @@ -308,7 +408,7 @@ const REGION_COMPARISON_ROWS_BY_COUNTRY: Record< string, StatsLocationItem[] > =

// Heuristic: a request whose `date` param is more than 1 day ago is treated as the
// comparison-period request. This works for the default `last-30-days` preset (primary
// date ≈ today, comparison date ≈ 30 days ago). It would misclassify a `today` preset
// date ~= today, comparison date ~= 30 days ago). It would misclassify a `today` preset
// (comparison date = yesterday, daysFromToday === 1), but the stories only use the default
// preset so this is fine in practice.
function isComparisonRequest( path: string ): boolean {
Expand Down Expand Up @@ -382,10 +482,16 @@ function getStatsMock( path: string ): unknown | null {
const withoutBase = path.slice( STATS_BASE.length );
const queryIndex = withoutBase.indexOf( '?' );
const subPath = queryIndex === -1 ? withoutBase : withoutBase.slice( 0, queryIndex );
const query = new URLSearchParams( queryIndex === -1 ? '' : withoutBase.slice( queryIndex + 1 ) );
const locationViewsMatch = subPath.match( /^\/location-views\/(country|region|city)$/ );

if ( subPath.startsWith( '/file-downloads' ) ) {
return isComparisonRequest( path ) ? MOCK_FILE_DOWNLOADS_COMPARISON : MOCK_FILE_DOWNLOADS;
}

const locationViewsMatch = subPath.match( /^\/location-views\/(country|region|city)$/ );
if ( locationViewsMatch ) {
const query = new URLSearchParams(
queryIndex === -1 ? '' : withoutBase.slice( queryIndex + 1 )
);
return buildStatsLocationViewsResponse(
locationViewsMatch[ 1 ] as GeoMode,
query,
Expand Down
15 changes: 15 additions & 0 deletions projects/packages/premium-analytics/src/dashboard-layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,21 @@ function seed_default_dashboard_layout( $dashboard_layout, $dashboard_name = ''
);
}

if ( ! in_array( 'default-file-downloads-widget-instance', $uuids, true ) ) {
$dashboard_layout[] = array(
'uuid' => 'default-file-downloads-widget-instance',
'type' => 'jpa/file-downloads',
'attributes' => array(
'max' => 10,
),
'placement' => array(
'width' => 1,
'height' => 2,
'order' => 6,
),
);
}

return $dashboard_layout;
}
add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\seed_default_dashboard_layout', 10, 2 );
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function test_seed_default_dashboard_layout_ignores_other_dashboards() {
}

/**
* The Premium Analytics dashboard receives the bundled default widgets.
* The Premium Analytics dashboard receives the UTM Insights widget.
*/
public function test_seed_default_dashboard_layout_adds_utm_insights_widget() {
$layout = seed_default_dashboard_layout( array(), DASHBOARD_NAME );
Expand Down Expand Up @@ -60,6 +60,33 @@ public function test_seed_default_dashboard_layout_adds_utm_insights_widget() {
);
}

/**
* The Premium Analytics dashboard receives the File Downloads widget.
*/
public function test_seed_default_dashboard_layout_adds_file_downloads_widget() {
$layout = seed_default_dashboard_layout( array(), DASHBOARD_NAME );
$layout_by_uuid = array_column( $layout, null, 'uuid' );
$file_downloads_widget_uuid = 'default-file-downloads-widget-instance';

$this->assertArrayHasKey( $file_downloads_widget_uuid, $layout_by_uuid );

$this->assertSame(
array(
'uuid' => $file_downloads_widget_uuid,
'type' => 'jpa/file-downloads',
'attributes' => array(
'max' => 10,
),
'placement' => array(
'width' => 1,
'height' => 2,
'order' => 6,
),
),
$layout_by_uuid[ $file_downloads_widget_uuid ]
);
}

/**
* An existing UTM Insights default instance is not duplicated.
*/
Expand All @@ -79,4 +106,31 @@ static function ( $widget ) {

$this->assertCount( 1, $utm_widgets );
}

/**
* An existing File Downloads default instance is not duplicated.
*/
public function test_seed_default_dashboard_layout_does_not_duplicate_file_downloads_widget() {
$existing_file_downloads_widget = array(
'uuid' => 'default-file-downloads-widget-instance',
'type' => 'jpa/file-downloads',
'attributes' => array( 'max' => 5 ),
'placement' => array(
'width' => 2,
'height' => 1,
'order' => 9,
),
);

$layout = seed_default_dashboard_layout( array( $existing_file_downloads_widget ), DASHBOARD_NAME );
$file_downloads_widgets = array_filter(
$layout,
static function ( $widget ) {
return 'default-file-downloads-widget-instance' === $widget['uuid'];
}
);

$this->assertCount( 1, $file_downloads_widgets );
$this->assertSame( $existing_file_downloads_widget, reset( $file_downloads_widgets ) );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@automattic/jetpack-premium-analytics-widget-file-downloads",
"version": "0.1.0-alpha",
"private": true,
"type": "module",
"dependencies": {
"@jetpack-premium-analytics/data": "link:../../packages/data",
"@jetpack-premium-analytics/widgets-toolkit": "link:../../packages/widgets-toolkit",
"@wordpress/i18n": "^6.9.0",
"@wordpress/icons": "^13.0.0",
"@wordpress/ui": "0.13.0",
"@wordpress/widget-primitives": "next"
}
}
Loading
Loading