Skip to content
This repository has been archived by the owner on Jun 7, 2022. It is now read-only.

Add explat variation for wc pay promotion #7554

Merged
merged 3 commits into from
Sep 10, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
176 changes: 176 additions & 0 deletions includes/class-experimental-abtest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<?php
/**
* NOTE: this is a temporary class and can be replaced by jetpack-abtest after
* https://github.com/Automattic/jetpack/issues/19596 has been fixed.
*
* A class that interacts with Explat A/B tests.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add some comments here with the source URL this was copied from and any notes about when we can remove this in the future (if applicable)?

*
* This class is experimental. It is a fork of the jetpack-abtest package and
* updated for use with ExPlat. These changes are planned to be contributed
* back to the upstream Jetpack package. If accepted, this class should then
* be superseded by the Jetpack class using Composer.
*
* This class should not be used externally.
*
* @package WooCommerce\Admin
* @link https://packagist.org/packages/automattic/jetpack-abtest
*/

namespace WooCommerce\Admin;

/**
* This class provides an interface to the Explat A/B tests.
*
* @internal This class is experimental and should not be used externally due to planned breaking changes.
*/
final class Experimental_Abtest {

/**
* A variable to hold the tests we fetched, and their variations for the current user.
*
* @var array
*/
private $tests = array();

/**
* ExPlat Anonymous ID.
*
* @var string
*/
private $anon_id = null;

/**
* ExPlat Platform name.
*
* @var string
*/
private $platform = 'woocommerce';

/**
* Whether trcking consent is given.
*
* @var bool
*/
private $consent = false;

/**
* Constructor.
*
* @param string $anon_id ExPlat anonymous ID.
* @param string $platform ExPlat platform name.
* @param bool $consent Whether tracking consent is given.
*/
public function __construct( string $anon_id, string $platform, bool $consent ) {
$this->anon_id = $anon_id;
$this->platform = $platform;
$this->consent = $consent;
}

/**
* Retrieve the test variation for a provided A/B test.
*
* @param string $test_name Name of the A/B test.
* @return mixed|null A/B test variation, or null on failure.
*/
public function get_variation( $test_name ) {
// Default to the control variation when users haven't consented to tracking.
if ( ! $this->consent ) {
return 'control';
}

$variation = $this->fetch_variation( $test_name );

// If there was an error retrieving a variation, conceal the error for the consumer.
if ( is_wp_error( $variation ) ) {
return 'control';
}

return $variation;
}

/**
* Fetch and cache the test variation for a provided A/B test from WP.com.
*
* ExPlat returns a null value when the assigned variation is control or
* an assignment has not been set. In these instances, this method returns
* a value of "control".
*
* @param string $test_name Name of the A/B test.
* @return array|\WP_Error A/B test variation, or error on failure.
*/
protected function fetch_variation( $test_name ) {
// Make sure test name exists.
if ( ! $test_name ) {
return new \WP_Error( 'test_name_not_provided', 'A/B test name has not been provided.' );
}

// Make sure test name is a valid one.
if ( ! preg_match( '/^[A-Za-z0-9_]+$/', $test_name ) ) {
return new \WP_Error( 'invalid_test_name', 'Invalid A/B test name.' );
}

// Return internal-cached test variations.
if ( isset( $this->tests[ $test_name ] ) ) {
return $this->tests[ $test_name ];
}

// Return external-cached test variations.
if ( ! empty( get_transient( 'abtest_variation_' . $test_name ) ) ) {
return get_transient( 'abtest_variation_' . $test_name );
}

// Make the request to the WP.com API.
$response = $this->request_variation( $test_name );

// Bail if there was an error or malformed response.
if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
return new \WP_Error( 'failed_to_fetch_data', 'Unable to fetch the requested data.' );
}

// Decode the results.
$results = json_decode( $response['body'], true );

// Bail if there were no results or there is no test variation returned.
if ( ! is_array( $results ) || empty( $results['variations'] ) ) {
return new \WP_Error( 'unexpected_data_format', 'Data was not returned in the expected format.' );
}

// Store the variation in our internal cache.
$this->tests[ $test_name ] = $results['variations'][ $test_name ];

$variation = $results['variations'][ $test_name ] ?? 'control';

// Store the variation in our external cache.
if ( ! empty( $results['ttl'] ) ) {
set_transient( 'abtest_variation_' . $test_name, $variation, $results['ttl'] );
}

return $variation;
}

/**
* Perform the request for a variation of a provided A/B test from WP.com.
*
* @param string $test_name Name of the A/B test.
* @return array|\WP_Error A/B test variation error on failure.
*/
protected function request_variation( $test_name ) {
$args = array(
'experiment_name' => $test_name,
'anon_id' => $this->anon_id,
);

$url = add_query_arg(
$args,
sprintf(
'https://public-api.wordpress.com/wpcom/v2/experiments/0.1.0/assignments/%s',
$this->platform
)
);

$get = wp_remote_get( $url );

return $get;
}
}

1 change: 1 addition & 0 deletions src/FeaturePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public function init() {
require_once WC_ADMIN_ABSPATH . '/includes/core-functions.php';
require_once WC_ADMIN_ABSPATH . '/includes/feature-config.php';
require_once WC_ADMIN_ABSPATH . '/includes/wc-admin-update-functions.php';
require_once WC_ADMIN_ABSPATH . '/includes/class-experimental-abtest.php';

register_activation_hook( WC_ADMIN_PLUGIN_FILE, array( $this, 'on_activation' ) );
register_deactivation_hook( WC_ADMIN_PLUGIN_FILE, array( $this, 'on_deactivation' ) );
Expand Down
2 changes: 1 addition & 1 deletion src/Features/WcPayPromotion/DataSourcePoller.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private static function read_data_source( $url ) {
*/
private static function merge_specs( $specs_to_merge_in, &$specs ) {
foreach ( $specs_to_merge_in as $spec ) {
$id = $spec->product;
$id = $spec->id;
$specs[ $id ] = $spec;
}
}
Expand Down
19 changes: 17 additions & 2 deletions src/Features/WcPayPromotion/Init.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,25 @@ public static function should_register_pre_install_wc_pay_promoted_gateway() {
}
$wc_pay_spec = self::get_wc_pay_promotion_spec();

if ( ! $wc_pay_spec ) {
if ( ! $wc_pay_spec || ! isset( $wc_pay_spec->additional_info ) || ! isset( $wc_pay_spec->additional_info->experiment_version ) ) {
return false;
}
return true;

$anon_id = isset( $_COOKIE['tk_ai'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['tk_ai'] ) ) : null;
$allow_tracking = 'yes' === get_option( 'woocommerce_allow_tracking' );
$abtest = new \WooCommerce\Admin\Experimental_Abtest(
$anon_id,
'woocommerce',
$allow_tracking
);

$variation_name = $abtest->get_variation( 'woocommerce_wc_pay_promotion_payment_methods_table_' . $wc_pay_spec->additional_info->experiment_version );
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to double check that experiment_version exists above first or are we sure it will always exist on this API going forward?

This will be a public API, so anything we add here will continue to be used for the same request in the future with this version of WooCommerce Admin.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I should probably do that as well


if ( 'treatment' === $variation_name ) {
return true;
}

return false;
}

/**
Expand Down