Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 72
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 / 72
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 / 64
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace GrowthExperiments\MentorDashboard\MenteeOverview;
4
5use FormatJson;
6use GrowthExperiments\Mentorship\Store\MentorStore;
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            }
119        }
120
121        // Delete all mentees of $mentor we did not update
122        $menteeIdsToDelete = array_diff(
123            array_map(
124                static function ( $mentee ) {
125                    return $mentee->getId();
126                },
127                $mentees
128            ),
129            $updatedMenteeIds
130        );
131        if ( $menteeIdsToDelete !== [] ) {
132            $dbw->newDeleteQueryBuilder()
133                ->deleteFrom( 'growthexperiments_mentee_data' )
134                ->where( [
135                    'mentee_id' => $menteeIdsToDelete
136                ] )
137                ->caller( __METHOD__ )
138                ->execute();
139            $this->lbFactory->waitForReplication();
140        }
141
142        $this->mentorProfilingInfo = $this->uncachedMenteeOverviewDataProvider
143            ->getProfilingInfo();
144
145        // if applicable, clear cache for the mentor we just updated
146        if ( $this->menteeOverviewDataProvider instanceof DatabaseMenteeOverviewDataProvider ) {
147            $this->menteeOverviewDataProvider->invalidateCacheForMentor( $mentor );
148        }
149
150        // update the last update timestamp
151        $this->userOptionsManager->setOption(
152            $mentor,
153            self::LAST_UPDATE_PREFERENCE,
154            wfTimestamp( TS_MW )
155        );
156        $this->userOptionsManager->saveOptions( $mentor );
157
158        return $updatedMenteeIds;
159    }
160}