Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
DatabaseMenteeOverviewDataProvider
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 6
90
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 makeCacheKey
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 invalidateCacheForMentor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 formatDataForMentee
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getFormattedDataForMentor
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
12
 getFormattedDataForMentee
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace GrowthExperiments\MentorDashboard\MenteeOverview;
4
5use GrowthExperiments\Mentorship\Store\MentorStore;
6use MediaWiki\Json\FormatJson;
7use MediaWiki\User\UserIdentity;
8use stdClass;
9use Wikimedia\LightweightObjectStore\ExpirationAwareness;
10use Wikimedia\ObjectCache\WANObjectCache;
11use Wikimedia\Rdbms\ILoadBalancer;
12
13/**
14 * Data provider for MenteeOverview module
15 *
16 * This data provider loads data from growthexperiments_mentee_data database
17 * table and caches them for a while.
18 *
19 * The table is populated with data from UncachedMenteeOverviewDataProvider, see
20 * that class for details about generating the data.
21 */
22class DatabaseMenteeOverviewDataProvider implements MenteeOverviewDataProvider, ExpirationAwareness {
23
24    private MentorStore $mentorStore;
25    private ILoadBalancer $growthLB;
26    protected WANObjectCache $wanCache;
27
28    /**
29     * @param WANObjectCache $wanCache
30     * @param MentorStore $mentorStore
31     * @param ILoadBalancer $growthLB
32     */
33    public function __construct(
34        WANObjectCache $wanCache,
35        MentorStore $mentorStore,
36        ILoadBalancer $growthLB
37    ) {
38        $this->wanCache = $wanCache;
39        $this->mentorStore = $mentorStore;
40        $this->growthLB = $growthLB;
41    }
42
43    /**
44     * @param UserIdentity $mentor
45     * @return string
46     */
47    private function makeCacheKey( UserIdentity $mentor ): string {
48        return $this->wanCache->makeKey(
49            'GrowthExperiments',
50            'MenteeOverviewDataProvider',
51            __CLASS__,
52            'Mentor',
53            $mentor->getId()
54        );
55    }
56
57    /**
58     * Invalidate cache for given mentor
59     * @param UserIdentity $mentor
60     */
61    public function invalidateCacheForMentor( UserIdentity $mentor ): void {
62        $this->wanCache->delete( $this->makeCacheKey( $mentor ) );
63    }
64
65    /**
66     * Decode data for particular mentee
67     *
68     * @param stdClass $row
69     * @return array
70     */
71    private function formatDataForMentee( stdClass $row ): array {
72        $input = FormatJson::decode( $row->mentee_data, true );
73        $input['user_id'] = $row->mentee_id;
74        $input['last_active'] ??= $input['last_edit'] ?? $input['registration'];
75        return $input;
76    }
77
78    /**
79     * @inheritDoc
80     */
81    public function getFormattedDataForMentor( UserIdentity $mentor ): array {
82        $method = __METHOD__;
83        return $this->wanCache->getWithSetCallback(
84            $this->makeCacheKey( $mentor ),
85            self::TTL_DAY,
86            function ( $oldValue, &$ttl, &$setOpts ) use ( $mentor, $method ) {
87                $mentees = $this->mentorStore->getMenteesByMentor( $mentor, MentorStore::ROLE_PRIMARY );
88                if ( $mentees === [] ) {
89                    $ttl = self::TTL_HOUR;
90                    return [];
91                }
92
93                $menteeIds = array_map( static function ( $mentee ) {
94                    return $mentee->getId();
95                }, $mentees );
96
97                $res = $this->growthLB->getConnection( DB_REPLICA )->newSelectQueryBuilder()
98                    ->select( [ 'mentee_id', 'mentee_data' ] )
99                    ->from( 'growthexperiments_mentee_data' )
100                    ->where( [ 'mentee_id' => $menteeIds ] )
101                    ->caller( $method )
102                    ->fetchResultSet();
103                $data = [];
104                foreach ( $res as $row ) {
105                    $data[] = $this->formatDataForMentee( $row );
106                }
107                return $data;
108            }
109        );
110    }
111
112    /**
113     * Fetch MenteeOverview data for a given mentee
114     *
115     * This is useful in other parts of GrowthExperiments that wish
116     * to reuse data MenteeOverview has available (Personalized praise, for
117     * example).
118     *
119     * @param UserIdentity $mentee
120     * @return array|null Formatted data if exists; null otherwise
121     */
122    public function getFormattedDataForMentee( UserIdentity $mentee ): ?array {
123        $res = $this->growthLB->getConnection( DB_REPLICA )->newSelectQueryBuilder()
124            ->select( [ 'mentee_id', 'mentee_data' ] )
125            ->from( 'growthexperiments_mentee_data' )
126            ->conds( [
127                'mentee_id' => $mentee->getId()
128            ] )
129            ->caller( __METHOD__ )
130            ->fetchRow();
131
132        if ( !$res ) {
133            // mentee not found
134            return null;
135        }
136
137        return $this->formatDataForMentee( $res );
138    }
139}