Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stats: Use option value instead of transient for cache buster #39887

Merged
merged 8 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: patch
Type: changed

Odyssey Stats cache busting: use optioin instead of transient
40 changes: 31 additions & 9 deletions projects/packages/stats-admin/src/class-odyssey-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,36 +75,58 @@ public function load_admin_scripts( $asset_handle, $asset_name, $options = array
/**
* Returns cache buster string for assets.
* Development mode doesn't need this, as it's handled by `Assets` class.
*
* @return string
*/
protected function get_cdn_asset_cache_buster() {
$now_in_ms = floor( microtime( true ) * 1000 );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['force_refresh'] ) ) {
set_transient( self::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY, floor( microtime( true ) * 1000 ), 15 * MINUTE_IN_SECONDS );
update_option( self::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY, $this->get_cache_buster_option_value( $now_in_ms ), false );
}

// Use cached cache buster in production.
$remote_asset_version = get_transient( self::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY );
$remote_asset_version = get_option( self::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY );

if ( ! empty( $remote_asset_version ) ) {
return $remote_asset_version;
$remote_asset_version = json_decode( $remote_asset_version, true );
// If cache buster is cached and not expired (valid in 15 min), return it.
if ( ! empty( $remote_asset_version['cache_buster'] ) && $remote_asset_version['cached_at'] > $now_in_ms - MINUTE_IN_SECONDS * 1000 * 15 ) {
return $remote_asset_version['cache_buster'];
}
}

// If no cached cache buster, we fetch it from CDN and set to transient.
$response = wp_remote_get( sprintf( self::ODYSSEY_CDN_URL, self::ODYSSEY_STATS_VERSION, 'build_meta.json?t=' . time() ), array( 'timeout' => 5 ) );
$response = wp_remote_get( sprintf( self::ODYSSEY_CDN_URL, self::ODYSSEY_STATS_VERSION, 'build_meta.json?t=' . $now_in_ms ), array( 'timeout' => 5 ) );

if ( is_wp_error( $response ) ) {
// fallback to the package version.
return Main::VERSION;
// fallback to current timestamp.
return (string) $now_in_ms;
}

$build_meta = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! empty( $build_meta['cache_buster'] ) ) {
// Cache the cache buster for 15 mins.
set_transient( self::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY, $build_meta['cache_buster'], 15 * MINUTE_IN_SECONDS );
update_option( self::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY, $this->get_cache_buster_option_value( $build_meta['cache_buster'] ), false );
return $build_meta['cache_buster'];
}

// fallback to the package version.
return Main::VERSION;
// fallback to current timestamp.
return (string) $now_in_ms;
}

/**
* Get the cache buster option value.
*
* @param string|int|float $cache_buster The cache buster.
* @return string|false
*/
protected function get_cache_buster_option_value( $cache_buster ) {
return wp_json_encode(
array(
'cache_buster' => (string) $cache_buster,
'cached_at' => floor( microtime( true ) * 1000 ), // milliseconds.
)
);
}
}
2 changes: 2 additions & 0 deletions projects/packages/stats-admin/tests/php/class-test-case.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function set_up() {

add_filter( 'jetpack_options', array( $this, 'mock_jetpack_site_connection_options' ), 10, 2 );
add_filter( 'pre_http_request', array( $this, 'plan_http_response_fixture' ), 10, 3 );
delete_option( Odyssey_Assets::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY );
}

/**
Expand All @@ -76,6 +77,7 @@ public function tear_down() {

remove_filter( 'pre_http_request', array( $this, 'plan_http_response_fixture' ) );
remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_site_connection_options' ) );
delete_option( Odyssey_Assets::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY );
}

/**
Expand Down
96 changes: 95 additions & 1 deletion projects/packages/stats-admin/tests/php/test-odyssey-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,114 @@
namespace Automattic\Jetpack\Stats_Admin;

use Automattic\Jetpack\Stats_Admin\Test_Case as Stats_Test_Case;
use WP_Error;

/**
* Unit tests for the Odyssey_Assets class.
*
* @package automattic/jetpack-stats-admin
*/
class Test_Odyssey_Assets extends Stats_Test_Case {

/**
* Test remote cache buster.
*/
public function test_get_cdn_asset_cache_buster() {
$this->assertEquals( 'calypso-4917-8664-g72a154d63a', $this->get_cdn_asset_cache_buster_callable() );
}

/**
* Test remote cache buster remote error.
*/
public function test_get_cdn_asset_cache_buster_remote_error() {
add_filter( 'pre_http_request', array( $this, 'break_cdn_cache_buster_request' ), 15, 3 );
$this->assertEquals( time(), floor( $this->get_cdn_asset_cache_buster_callable() / 1000 ) );
remove_filter( 'pre_http_request', array( $this, 'break_cdn_cache_buster_request' ), 15 );
}

/**
* Test already cached cache buster.
*/
public function test_get_cdn_asset_cache_buster_already_cached() {
update_option(
Odyssey_Assets::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY,
wp_json_encode(
array(
'cache_buster' => 'calypso-4917-8664-123456',
'cached_at' => floor( microtime( true ) * 1000 ), // milliseconds.
)
),
false
);
$this->assertEquals( 'calypso-4917-8664-123456', $this->get_cdn_asset_cache_buster_callable() );
}

/**
* Test already cached cache buster expired.
*/
public function test_get_cdn_asset_cache_buster_already_cached_expired() {
update_option(
Odyssey_Assets::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY,
wp_json_encode(
array(
'cache_buster' => 'calypso-4917-8664-123456',
'cached_at' => floor( microtime( true ) * 1000 - MINUTE_IN_SECONDS * 1000 * 20 ), // milliseconds.
)
),
false
);
$this->assertEquals( 'calypso-4917-8664-g72a154d63a', $this->get_cdn_asset_cache_buster_callable() );
}

/**
* Test already cached cache buster expired and failed to fetch new one.
*/
public function test_get_cdn_asset_cache_buster_failed_to_fetch() {
add_filter( 'pre_http_request', array( $this, 'break_cdn_cache_buster_request' ), 15, 3 );
update_option(
Odyssey_Assets::ODYSSEY_STATS_CACHE_BUSTER_CACHE_KEY,
wp_json_encode(
array(
'cache_buster' => 'calypso-4917-8664-123456',
'cached_at' => floor( microtime( true ) * 1000 - MINUTE_IN_SECONDS * 1000 * 20 ), // milliseconds.
)
),
false
);
$this->assertEquals( time(), floor( $this->get_cdn_asset_cache_buster_callable() / 1000 ) );
remove_filter( 'pre_http_request', array( $this, 'break_cdn_cache_buster_request' ), 15 );
}

/**
* Test force refresh cache buster.
*/
public function test_get_cdn_asset_cache_buster_force_refresh_expired() {
$_GET['force_refresh'] = 1;
$this->assertEquals( time(), floor( $this->get_cdn_asset_cache_buster_callable() / 1000 ) );
}

/**
* Test remote cache buster.
*
* @param mixed $response The response array.
* @param mixed $parsed_args The parsed args.
* @param mixed $url The URL.
* @return WP_Error | void
*/
public function break_cdn_cache_buster_request( $response, $parsed_args, $url ) {
if ( strpos( $url, '/build_meta.json' ) !== false ) {
return new WP_Error( 500, 'Internal Server Error' );
}
}

/**
* Get CDN asset cache buster.
*/
protected function get_cdn_asset_cache_buster_callable() {
$odyssey_assets = new Odyssey_Assets();
$get_cdn_asset_cache_buster = new \ReflectionMethod( $odyssey_assets, 'get_cdn_asset_cache_buster' );
$get_cdn_asset_cache_buster->setAccessible( true );
$this->assertEquals( 'calypso-4917-8664-g72a154d63a', $get_cdn_asset_cache_buster->invoke( $odyssey_assets ) );

return $get_cdn_asset_cache_buster->invoke( $odyssey_assets );
}
}
Loading