Skip to content

Commit

Permalink
Stats: Use option value instead of transient for cache buster (#39887)
Browse files Browse the repository at this point in the history
  • Loading branch information
kangzj authored and gogdzl committed Oct 25, 2024
1 parent 7587a57 commit abc6b8d
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 10 deletions.
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 );
}
}

0 comments on commit abc6b8d

Please sign in to comment.