Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 49
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 / 49
0.00% covered (danger)
0.00%
0 / 6
110
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 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getFormattedDataForMentor
0.00% covered (danger)
0.00%
0 / 22
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 FormatJson;
6use GrowthExperiments\Mentorship\Store\MentorStore;
7use MediaWiki\User\UserIdentity;
8use stdClass;
9use WANObjectCache;
10use Wikimedia\LightweightObjectStore\ExpirationAwareness;
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        if ( !array_key_exists( 'last_active', $input ) ) {
75            $input['last_active'] = $input['last_edit'] ?? $input['registration'];
76        }
77
78        return $input;
79    }
80
81    /**
82     * @inheritDoc
83     */
84    public function getFormattedDataForMentor( UserIdentity $mentor ): array {
85        return $this->wanCache->getWithSetCallback(
86            $this->makeCacheKey( $mentor ),
87            self::TTL_DAY,
88            function ( $oldValue, &$ttl, &$setOpts ) use ( $mentor ) {
89                $mentees = $this->mentorStore->getMenteesByMentor( $mentor, MentorStore::ROLE_PRIMARY );
90                if ( $mentees === [] ) {
91                    $ttl = self::TTL_HOUR;
92                    return [];
93                }
94
95                $menteeIds = array_map( static function ( $mentee ) {
96                    return $mentee->getId();
97                }, $mentees );
98
99                $res = $this->growthLB->getConnection( DB_REPLICA )->newSelectQueryBuilder()
100                    ->select( [ 'mentee_id', 'mentee_data' ] )
101                    ->from( 'growthexperiments_mentee_data' )
102                    ->where( [ 'mentee_id' => $menteeIds ] )
103                    ->fetchResultSet();
104                $data = [];
105                foreach ( $res as $row ) {
106                    $data[] = $this->formatDataForMentee( $row );
107                }
108                return $data;
109            }
110        );
111    }
112
113    /**
114     * Fetch MenteeOverview data for a given mentee
115     *
116     * This is useful in other parts of GrowthExperiments that wish
117     * to reuse data MenteeOverview has available (Personalized praise, for
118     * example).
119     *
120     * @param UserIdentity $mentee
121     * @return array|null Formatted data if exists; null otherwise
122     */
123    public function getFormattedDataForMentee( UserIdentity $mentee ): ?array {
124        $res = $this->growthLB->getConnection( DB_REPLICA )->newSelectQueryBuilder()
125            ->select( [ 'mentee_id', 'mentee_data' ] )
126            ->from( 'growthexperiments_mentee_data' )
127            ->conds( [
128                'mentee_id' => $mentee->getId()
129            ] )
130            ->caller( __METHOD__ )
131            ->fetchRow();
132
133        if ( !$res ) {
134            // mentee not found
135            return null;
136        }
137
138        return $this->formatDataForMentee( $res );
139    }
140}