Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
MenteeOverviewDataUpdater
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 4
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 setBatchSize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMentorProfilingInfo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 updateDataForMentor
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace GrowthExperiments\MentorDashboard\MenteeOverview;
4
5use GrowthExperiments\Mentorship\Store\MentorStore;
6use MediaWiki\Json\FormatJson;
7use MediaWiki\User\Options\UserOptionsManager;
8use MediaWiki\User\UserIdentity;
9use Wikimedia\Rdbms\ILoadBalancer;
10use Wikimedia\Rdbms\LBFactory;
11
12/**
13 * Updates growthexperiments_mentee_data
14 *
15 * WARNING: This class may submit heavy queries that take minutes (or hours)
16 * to complete. It may only be used from CLI scripts or MediaWiki jobs.
17 */
18class MenteeOverviewDataUpdater {
19    public const LAST_UPDATE_PREFERENCE = 'growthexperiments-mentor-dashboard-last-update';
20
21    private UncachedMenteeOverviewDataProvider $uncachedMenteeOverviewDataProvider;
22    private MenteeOverviewDataProvider $menteeOverviewDataProvider;
23    private MentorStore $mentorStore;
24    private UserOptionsManager $userOptionsManager;
25    private LBFactory $lbFactory;
26    private ILoadBalancer $growthLoadBalancer;
27    private int $batchSize = 200;
28    private array $mentorProfilingInfo = [];
29
30    /**
31     * @param UncachedMenteeOverviewDataProvider $uncachedMenteeOverviewDataProvider
32     * @param MenteeOverviewDataProvider $menteeOverviewDataProvider
33     * @param MentorStore $mentorStore
34     * @param UserOptionsManager $userOptionsManager
35     * @param LBFactory $lbFactory
36     * @param ILoadBalancer $growthLoadBalancer
37     */
38    public function __construct(
39        UncachedMenteeOverviewDataProvider $uncachedMenteeOverviewDataProvider,
40        MenteeOverviewDataProvider $menteeOverviewDataProvider,
41        MentorStore $mentorStore,
42        UserOptionsManager $userOptionsManager,
43        LBFactory $lbFactory,
44        ILoadBalancer $growthLoadBalancer
45    ) {
46        $this->uncachedMenteeOverviewDataProvider = $uncachedMenteeOverviewDataProvider;
47        $this->menteeOverviewDataProvider = $menteeOverviewDataProvider;
48        $this->mentorStore = $mentorStore;
49        $this->userOptionsManager = $userOptionsManager;
50        $this->lbFactory = $lbFactory;
51        $this->growthLoadBalancer = $growthLoadBalancer;
52    }
53
54    /**
55     * @param int $batchSize
56     */
57    public function setBatchSize( int $batchSize ) {
58        $this->batchSize = $batchSize;
59    }
60
61    /**
62     * @return array
63     */
64    public function getMentorProfilingInfo(): array {
65        return $this->mentorProfilingInfo;
66    }
67
68    /**
69     * @param UserIdentity $mentor
70     * @return int[] List of IDs this function updated
71     */
72    public function updateDataForMentor( UserIdentity $mentor ): array {
73        $this->mentorProfilingInfo = [];
74
75        $thisBatch = 0;
76
77        $dbw = $this->growthLoadBalancer->getConnection( DB_PRIMARY );
78        $dbr = $this->growthLoadBalancer->getConnection( DB_REPLICA );
79
80        $data = $this->uncachedMenteeOverviewDataProvider->getFormattedDataForMentor( $mentor );
81        $mentees = $this->mentorStore->getMenteesByMentor( $mentor, MentorStore::ROLE_PRIMARY );
82        $updatedMenteeIds = [];
83        foreach ( $data as $menteeId => $menteeData ) {
84            $encodedData = FormatJson::encode( $menteeData );
85            $storedEncodedData = $dbr->newSelectQueryBuilder()
86                ->select( 'mentee_data' )
87                ->from( 'growthexperiments_mentee_data' )
88                ->where( [ 'mentee_id' => $menteeId ] )
89                ->caller( __METHOD__ )->fetchField();
90            if ( $storedEncodedData === false ) {
91                // Row doesn't exist yet, insert it
92                $dbw->newInsertQueryBuilder()
93                    ->insertInto( 'growthexperiments_mentee_data' )
94                    ->row( [
95                        'mentee_id' => $menteeId,
96                        'mentee_data' => $encodedData
97                    ] )
98                    ->caller( __METHOD__ )
99                    ->execute();
100            } else {
101                // Row exists, if anything changed, update
102                if ( FormatJson::decode( $storedEncodedData, true ) !== $menteeData ) {
103                    $dbw->newUpdateQueryBuilder()
104                        ->update( 'growthexperiments_mentee_data' )
105                        ->set( [ 'mentee_data' => $encodedData ] )
106                        ->where( [ 'mentee_id' => $menteeId ] )
107                        ->caller( __METHOD__ )
108                        ->execute();
109                }
110            }
111
112            $thisBatch++;
113            $updatedMenteeIds[] = $menteeId;
114
115            if ( $thisBatch >= $this->batchSize ) {
116                $thisBatch = 0;
117                $this->lbFactory->waitForReplication();
118                $this->lbFactory->autoReconfigure();
119            }
120        }
121
122        // Delete all mentees of $mentor we did not update
123        $menteeIdsToDelete = array_diff(
124            array_map(
125                static function ( $mentee ) {
126                    return $mentee->getId();
127                },
128                $mentees
129            ),
130            $updatedMenteeIds
131        );
132        if ( $menteeIdsToDelete !== [] ) {
133            $dbw->newDeleteQueryBuilder()
134                ->deleteFrom( 'growthexperiments_mentee_data' )
135                ->where( [
136                    'mentee_id' => $menteeIdsToDelete
137                ] )
138                ->caller( __METHOD__ )
139                ->execute();
140            $this->lbFactory->waitForReplication();
141        }
142
143        $this->mentorProfilingInfo = $this->uncachedMenteeOverviewDataProvider
144            ->getProfilingInfo();
145
146        // if applicable, clear cache for the mentor we just updated
147        if ( $this->menteeOverviewDataProvider instanceof DatabaseMenteeOverviewDataProvider ) {
148            $this->menteeOverviewDataProvider->invalidateCacheForMentor( $mentor );
149        }
150
151        // update the last update timestamp
152        $this->userOptionsManager->setOption(
153            $mentor,
154            self::LAST_UPDATE_PREFERENCE,
155            wfTimestamp( TS_MW )
156        );
157        $this->userOptionsManager->saveOptions( $mentor );
158
159        return $updatedMenteeIds;
160    }
161}