Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
54.84% covered (warning)
54.84%
17 / 31
14.29% covered (danger)
14.29%
1 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExperimentUserManager
54.84% covered (warning)
54.84%
17 / 31
14.29% covered (danger)
14.29%
1 / 7
22.15
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 setPlatform
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVariant
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 setVariant
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 isUserInVariant
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isValidVariant
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRandomVariant
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
1<?php
2
3namespace GrowthExperiments;
4
5use MediaWiki\Config\ServiceOptions;
6use MediaWiki\User\Options\UserOptionsLookup;
7use MediaWiki\User\Options\UserOptionsManager;
8use MediaWiki\User\UserIdentity;
9
10/**
11 * Service for handling experiment / variant related functions for users.
12 */
13class ExperimentUserManager {
14
15    private ServiceOptions $options;
16    private UserOptionsLookup $userOptionsLookup;
17    private UserOptionsManager $userOptionsManager;
18
19    /** @var string|null One of 'mobile' or 'desktop' */
20    private ?string $platform;
21
22    public const CONSTRUCTOR_OPTIONS = [
23        'GEHomepageDefaultVariant',
24        'GEHomepageNewAccountVariantsByPlatform',
25    ];
26
27    /**
28     * @param ServiceOptions $options
29     * @param UserOptionsManager $userOptionsManager
30     * @param UserOptionsLookup $userOptionsLookup
31     * @param string|null $platform
32     */
33    public function __construct(
34        ServiceOptions $options,
35        UserOptionsManager $userOptionsManager,
36        UserOptionsLookup $userOptionsLookup,
37        ?string $platform = null
38    ) {
39        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
40        $this->options = $options;
41        $this->userOptionsLookup = $userOptionsLookup;
42        $this->userOptionsManager = $userOptionsManager;
43        $this->platform = $platform;
44    }
45
46    /**
47     * Specify if the experiment manager is in a desktop/mobile platform context.
48     *
49     * @param string $platform One of "mobile" or "desktop"
50     */
51    public function setPlatform( string $platform ): void {
52        $this->platform = $platform;
53    }
54
55    /**
56     * @param UserIdentity $user
57     * @return string
58     */
59    public function getVariant( UserIdentity $user ): string {
60        $variant = $this->userOptionsLookup->getOption(
61            $user,
62            VariantHooks::USER_PREFERENCE
63        );
64        if ( !in_array( $variant, VariantHooks::VARIANTS ) ) {
65            $variant = $this->options->get( 'GEHomepageDefaultVariant' );
66        }
67        return $variant;
68    }
69
70    /**
71     * Set (but does not save) the variant for a user.
72     *
73     * @param UserIdentity $user
74     * @param string $variant
75     */
76    public function setVariant( UserIdentity $user, string $variant ): void {
77        $this->userOptionsManager->setOption(
78            $user,
79            VariantHooks::USER_PREFERENCE,
80            $variant
81        );
82    }
83
84    /**
85     * @param UserIdentity $user
86     * @param string|string[] $variant
87     * @return bool
88     */
89    public function isUserInVariant( UserIdentity $user, $variant ): bool {
90        return in_array( $this->getVariant( $user ), (array)$variant );
91    }
92
93    /**
94     * @param string $variant
95     * @return bool
96     */
97    public function isValidVariant( string $variant ): bool {
98        return in_array( $variant, VariantHooks::VARIANTS );
99    }
100
101    /**
102     * Get a random variant according to the distribution defined in $wgGEHomepageNewAccountVariantsByPlatform.
103     *
104     * @return string
105     */
106    public function getRandomVariant(): string {
107        $variantProbabilities = $this->options->get( 'GEHomepageNewAccountVariantsByPlatform' );
108        $random = rand( 0, 99 );
109
110        $variant = $this->options->get( 'GEHomepageDefaultVariant' );
111        foreach ( $variantProbabilities as $candidateVariant => $percentageForVariant ) {
112            if ( !$this->isValidVariant( $candidateVariant ) ) {
113                continue;
114            }
115            if ( $random < $percentageForVariant[$this->platform] ) {
116                $variant = $candidateVariant;
117                break;
118            }
119            $random -= $percentageForVariant[$this->platform];
120        }
121        return $variant;
122    }
123}