Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
27.27% covered (danger)
27.27%
9 / 33
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Launchpad_Personalization_Experiment
27.27% covered (danger)
27.27%
9 / 33
33.33% covered (danger)
33.33%
1 / 3
89.40
0.00% covered (danger)
0.00%
0 / 1
 get_variation
46.15% covered (danger)
46.15%
6 / 13
0.00% covered (danger)
0.00%
0 / 1
6.50
 fetch_variation
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
56
 normalize
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Resolves the wpcom_launchpad_personalization_202607_v1 experiment variation server-side.
4 *
5 * @package automattic/jetpack-mu-wpcom
6 */
7
8namespace Automattic\Jetpack\Jetpack_Mu_Wpcom;
9
10use Automattic\Jetpack\Connection\Client;
11use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12use Automattic\Jetpack\Status\Host;
13
14/**
15 * Reads the launchpad-personalization variation for the current user.
16 *
17 * Simple sites use the native non-assigning ExPlat read; Atomic sites fetch the
18 * assignment over the connected-user REST endpoint (segmentation-bounded). The result
19 * is transient-cached per user for an hour, mirroring Help_Center::is_menu_panel_enabled().
20 */
21class Launchpad_Personalization_Experiment {
22
23    const EXPERIMENT_NAME = 'wpcom_launchpad_personalization_202607_v1';
24
25    /**
26     * The current user's variation: 'control', 'ai_launchpad', or 'no_guidance'.
27     *
28     * @return string
29     */
30    public static function get_variation() {
31        /**
32         * Overrides the resolved variation (testing / manual QA). Return one of the variation strings, or null to use ExPlat.
33         *
34         * @param string|null $override The forced variation, or null.
35         */
36        $override = apply_filters( 'wpcom_launchpad_personalization_variation', null );
37        if ( null !== $override ) {
38            return self::normalize( $override );
39        }
40
41        $user_id = get_current_user_id();
42        if ( ! $user_id ) {
43            return 'control';
44        }
45
46        $cache_key = 'launchpad-personalization-variation-' . $user_id;
47        $cached    = get_transient( $cache_key );
48        if ( false !== $cached ) {
49            return (string) $cached;
50        }
51
52        $variation = self::normalize( self::fetch_variation() );
53        set_transient( $cache_key, $variation, HOUR_IN_SECONDS );
54
55        return $variation;
56    }
57
58    /**
59     * Fetch the raw variation name from ExPlat, or null.
60     *
61     * @return string|null
62     */
63    private static function fetch_variation() {
64        if ( ( new Host() )->is_wpcom_simple() ) {
65            // Non-assigning read: rendering wp-admin must never create an assignment.
66            if ( function_exists( '\ExPlat\get_current_user_assignment' ) ) {
67                // The \ExPlat\ helpers live in wpcom, outside this monorepo, so Phan can't see them.
68                // @phan-suppress-next-line PhanUndeclaredFunction
69                return \ExPlat\get_current_user_assignment( self::EXPERIMENT_NAME );
70            }
71            return null;
72        }
73
74        if ( ! ( new Connection_Manager() )->is_user_connected() ) {
75            return null;
76        }
77
78        // Atomic: no local ExPlat engine — ask wpcom as the connected user. The experiment is
79        // registered on the `calypso` platform, so the assignment must be fetched from there.
80        $request_path = '/experiments/0.1.0/assignments/calypso';
81        $response     = Client::wpcom_json_api_request_as_user(
82            add_query_arg( array( 'experiment_names' => self::EXPERIMENT_NAME ), $request_path ),
83            'v2'
84        );
85
86        if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
87            return null;
88        }
89
90        $data = json_decode( wp_remote_retrieve_body( $response ), true );
91        if ( isset( $data['variations'][ self::EXPERIMENT_NAME ] ) ) {
92            return $data['variations'][ self::EXPERIMENT_NAME ];
93        }
94
95        return null;
96    }
97
98    /**
99     * Map any variation onto a known value; unknown/null becomes 'control'.
100     *
101     * @param string|null $variation The raw variation name.
102     * @return string
103     */
104    private static function normalize( $variation ) {
105        if ( 'ai_launchpad' === $variation || 'no_guidance' === $variation ) {
106            return $variation;
107        }
108        return 'control';
109    }
110}