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