Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 99 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
UpdateMenteeData | |
0.00% |
0 / 93 |
|
0.00% |
0 / 5 |
462 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
initServices | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
addProfilingInfoForMentor | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
getSummarizedProfilingInfoInSeconds | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
execute | |
0.00% |
0 / 63 |
|
0.00% |
0 / 1 |
210 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\Maintenance; |
4 | |
5 | use GrowthExperiments\GrowthExperimentsServices; |
6 | use GrowthExperiments\MentorDashboard\MenteeOverview\MenteeOverviewDataUpdater; |
7 | use GrowthExperiments\Mentorship\Provider\MentorProvider; |
8 | use GrowthExperiments\WikiConfigException; |
9 | use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface; |
10 | use Maintenance; |
11 | use MediaWiki\MediaWikiServices; |
12 | use MediaWiki\User\UserFactory; |
13 | use Wikimedia\Rdbms\ILoadBalancer; |
14 | |
15 | $IP = getenv( 'MW_INSTALL_PATH' ); |
16 | if ( $IP === false ) { |
17 | $IP = __DIR__ . '/../../..'; |
18 | } |
19 | require_once "$IP/maintenance/Maintenance.php"; |
20 | |
21 | class UpdateMenteeData extends Maintenance { |
22 | |
23 | /** @var MenteeOverviewDataUpdater */ |
24 | private $menteeOverviewDataUpdater; |
25 | |
26 | /** @var MentorProvider */ |
27 | private $mentorProvider; |
28 | |
29 | /** @var UserFactory */ |
30 | private $userFactory; |
31 | |
32 | /** @var ILoadBalancer */ |
33 | private $growthLoadBalancer; |
34 | |
35 | /** @var StatsdDataFactoryInterface */ |
36 | private $dataFactory; |
37 | |
38 | /** @var array */ |
39 | private $detailedProfilingInfo = []; |
40 | |
41 | public function __construct() { |
42 | parent::__construct(); |
43 | $this->setBatchSize( 200 ); |
44 | $this->requireExtension( 'GrowthExperiments' ); |
45 | |
46 | $this->addDescription( 'Update growthexperiments_mentee_data database table' ); |
47 | $this->addOption( 'force', 'Do the update even if GEMentorDashboardEnabled is false' ); |
48 | $this->addOption( 'mentor', 'Username of the mentor to update the data for', false, true ); |
49 | $this->addOption( 'statsd', 'Send timing information to statsd' ); |
50 | $this->addOption( 'verbose', 'Output detailed profiling information' ); |
51 | $this->addOption( |
52 | 'dbshard', |
53 | 'ID of the DB shard this script runs at', |
54 | false, |
55 | true |
56 | ); |
57 | } |
58 | |
59 | private function initServices() { |
60 | $services = MediaWikiServices::getInstance(); |
61 | $geServices = GrowthExperimentsServices::wrap( $services ); |
62 | |
63 | $this->menteeOverviewDataUpdater = $geServices->getMenteeOverviewDataUpdater(); |
64 | $this->menteeOverviewDataUpdater->setBatchSize( $this->getBatchSize() ); |
65 | $this->mentorProvider = $geServices->getMentorProvider(); |
66 | $this->userFactory = $services->getUserFactory(); |
67 | $this->growthLoadBalancer = $geServices->getLoadBalancer(); |
68 | $this->dataFactory = $services->getStatsdDataFactory(); |
69 | } |
70 | |
71 | private function addProfilingInfoForMentor( array $mentorProfilingInfo ): void { |
72 | foreach ( $mentorProfilingInfo as $section => $seconds ) { |
73 | if ( !array_key_exists( $section, $this->detailedProfilingInfo ) ) { |
74 | $this->detailedProfilingInfo[$section] = 0; |
75 | } |
76 | |
77 | $this->detailedProfilingInfo[$section] += $seconds; |
78 | } |
79 | } |
80 | |
81 | private function getSummarizedProfilingInfoInSeconds(): array { |
82 | $res = []; |
83 | foreach ( $this->detailedProfilingInfo as $section => $seconds ) { |
84 | $res[$section] = round( $seconds, 2 ); |
85 | } |
86 | return $res; |
87 | } |
88 | |
89 | public function execute() { |
90 | if ( |
91 | !$this->getConfig()->get( 'GEMentorDashboardEnabled' ) && |
92 | !$this->hasOption( 'force' ) |
93 | ) { |
94 | $this->output( "Mentor dashboard is disabled.\n" ); |
95 | return; |
96 | } |
97 | |
98 | $this->initServices(); |
99 | |
100 | $startTime = time(); |
101 | |
102 | if ( $this->hasOption( 'mentor' ) ) { |
103 | $mentors = [ $this->getOption( 'mentor' ) ]; |
104 | } else { |
105 | try { |
106 | $mentors = $this->mentorProvider->getMentors(); |
107 | } catch ( WikiConfigException $e ) { |
108 | $this->fatalError( 'List of mentors cannot be fetched.' ); |
109 | } |
110 | } |
111 | |
112 | $allUpdatedMenteeIds = []; |
113 | $dbw = $this->growthLoadBalancer->getConnection( DB_PRIMARY ); |
114 | foreach ( $mentors as $mentorRaw ) { |
115 | $mentor = $this->userFactory->newFromName( $mentorRaw ); |
116 | if ( $mentor === null ) { |
117 | $this->output( 'Skipping ' . $mentorRaw . ", invalid user\n" ); |
118 | continue; |
119 | } |
120 | |
121 | $updatedMenteeIds = $this->menteeOverviewDataUpdater->updateDataForMentor( $mentor ); |
122 | $allUpdatedMenteeIds = array_merge( $allUpdatedMenteeIds, $updatedMenteeIds ); |
123 | $this->addProfilingInfoForMentor( |
124 | $this->menteeOverviewDataUpdater->getMentorProfilingInfo() |
125 | ); |
126 | } |
127 | |
128 | // Delete all mentees recorded in the table which were not updated |
129 | // This cannot happen when --mentor was passed, as that would delete |
130 | // most of the data. |
131 | if ( !$this->hasOption( 'mentor' ) ) { |
132 | $menteeIdsToDelete = array_diff( |
133 | array_map( |
134 | 'intval', |
135 | $dbw->newSelectQueryBuilder() |
136 | ->select( 'mentee_id' ) |
137 | ->from( 'growthexperiments_mentee_data' ) |
138 | ->caller( __METHOD__ )->fetchFieldValues() |
139 | ), |
140 | $allUpdatedMenteeIds |
141 | ); |
142 | if ( $menteeIdsToDelete !== [] ) { |
143 | $dbw->newDeleteQueryBuilder() |
144 | ->deleteFrom( 'growthexperiments_mentee_data' ) |
145 | ->where( [ |
146 | 'mentee_id' => $menteeIdsToDelete |
147 | ] ) |
148 | ->caller( __METHOD__ ) |
149 | ->execute(); |
150 | } |
151 | } |
152 | |
153 | $totalTime = time() - $startTime; |
154 | $profilingInfo = $this->getSummarizedProfilingInfoInSeconds(); |
155 | |
156 | if ( $this->hasOption( 'verbose' ) ) { |
157 | $this->output( "Profiling data:\n" ); |
158 | foreach ( $profilingInfo as $section => $seconds ) { |
159 | $this->output( " * {$section}: {$seconds} seconds\n" ); |
160 | } |
161 | $this->output( "===============\n" ); |
162 | } |
163 | |
164 | if ( $this->hasOption( 'statsd' ) && $this->hasOption( 'dbshard' ) ) { |
165 | $this->dataFactory->timing( |
166 | 'timing.growthExperiments.updateMenteeData.' . $this->getOption( 'dbshard' ) . '.total', |
167 | $totalTime |
168 | ); |
169 | |
170 | foreach ( $profilingInfo as $section => $seconds ) { |
171 | $this->dataFactory->timing( |
172 | 'timing.growthExperiments.updateMenteeData.' . |
173 | $this->getOption( 'dbshard' ) . |
174 | '.' . |
175 | $section, |
176 | $seconds |
177 | ); |
178 | } |
179 | } |
180 | |
181 | $this->output( "Done. Took {$totalTime} seconds.\n" ); |
182 | } |
183 | } |
184 | |
185 | $maintClass = UpdateMenteeData::class; |
186 | require_once RUN_MAINTENANCE_IF_MAIN; |