Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 57 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
OresMetadata | |
0.00% |
0 / 57 |
|
0.00% |
0 / 8 |
240 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
newFromGlobalState | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
getMetadata | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getArticleQualityClass | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
getDraftQualityClass | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
classToMessage | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
fetchScores | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
20 | |||
getORESScores | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software: you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by |
6 | * the Free Software Foundation, either version 3 of the License, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | namespace MediaWiki\Extension\PageTriage; |
19 | |
20 | use LogicException; |
21 | use MediaWiki\Context\IContextSource; |
22 | use ORES\Services\ORESServices; |
23 | use ORES\Storage\ModelLookup; |
24 | use ORES\Storage\ThresholdLookup; |
25 | use Wikimedia\Rdbms\IResultWrapper; |
26 | |
27 | /** |
28 | * Helper class to add metadata to articles in the list view of Special:NewPagesFeed. |
29 | * |
30 | * @package MediaWiki\Extension\PageTriage |
31 | */ |
32 | class OresMetadata { |
33 | |
34 | /** |
35 | * @var ThresholdLookup |
36 | */ |
37 | private $thresholdLookup; |
38 | |
39 | /** |
40 | * @var ModelLookup |
41 | */ |
42 | private $modelLookup; |
43 | |
44 | /** |
45 | * @var array |
46 | */ |
47 | private $oresModelClasses; |
48 | |
49 | /** |
50 | * @var IContextSource |
51 | */ |
52 | private $requestContext; |
53 | |
54 | /** |
55 | * @var array |
56 | */ |
57 | private $scores; |
58 | |
59 | /** |
60 | * @var string[] Map of ORES class names from thresholds lookup mapped to translatable strings. |
61 | */ |
62 | private const ORES_CLASS_TO_MSG_KEY = [ |
63 | 'Stub' => 'pagetriage-filter-stat-predicted-class-stub', |
64 | 'Start' => 'pagetriage-filter-stat-predicted-class-start', |
65 | 'C' => 'pagetriage-filter-stat-predicted-class-c', |
66 | 'B' => 'pagetriage-filter-stat-predicted-class-b', |
67 | 'GA' => 'pagetriage-filter-stat-predicted-class-good', |
68 | 'FA' => 'pagetriage-filter-stat-predicted-class-featured', |
69 | 'vandalism' => 'pagetriage-filter-stat-predicted-issues-vandalism', |
70 | 'attack' => 'pagetriage-filter-stat-predicted-issues-attack', |
71 | 'spam' => 'pagetriage-filter-stat-predicted-issues-spam', |
72 | 'OK' => false, |
73 | ]; |
74 | |
75 | /** |
76 | * OresMetadata constructor. |
77 | * @param ThresholdLookup $thresholdLookup |
78 | * @param ModelLookup $modelLookup |
79 | * @param array $oresModelClasses |
80 | * @param IContextSource $requestContext |
81 | * @param int[] $pageIds |
82 | */ |
83 | public function __construct( |
84 | ThresholdLookup $thresholdLookup, |
85 | ModelLookup $modelLookup, |
86 | array $oresModelClasses, |
87 | IContextSource $requestContext, |
88 | $pageIds |
89 | ) { |
90 | $this->thresholdLookup = $thresholdLookup; |
91 | $this->modelLookup = $modelLookup; |
92 | $this->oresModelClasses = $oresModelClasses; |
93 | $this->requestContext = $requestContext; |
94 | |
95 | // Pre-fetch the ORES scores for all the pages of interest |
96 | $this->scores = $this->fetchScores( $pageIds ); |
97 | } |
98 | |
99 | /** |
100 | * Create an instance of OresMetadata by getting dependencies from |
101 | * global variables and static ORESServices |
102 | * |
103 | * @param IContextSource $context |
104 | * @param int[] $pageIds |
105 | * @return OresMetadata |
106 | */ |
107 | public static function newFromGlobalState( IContextSource $context, $pageIds ) { |
108 | global $wgOresModelClasses; |
109 | return new self( |
110 | ORESServices::getThresholdLookup(), |
111 | ORESServices::getModelLookup(), |
112 | $wgOresModelClasses, |
113 | $context, |
114 | $pageIds |
115 | ); |
116 | } |
117 | |
118 | /** |
119 | * Get ORES metadata (articlequality, draftquality) from the database for an article. |
120 | * |
121 | * @param int $pageId |
122 | * @return array |
123 | * An array to merge in with other metadata for the article. |
124 | */ |
125 | public function getMetadata( $pageId ) { |
126 | return $this->scores[ $pageId ]; |
127 | } |
128 | |
129 | /** |
130 | * @param float $probability |
131 | * @return string Name of the class corresponding to the given probability |
132 | */ |
133 | private function getArticleQualityClass( $probability ) { |
134 | $thresholds = $this->thresholdLookup->getThresholds( 'articlequality' ); |
135 | foreach ( $thresholds as $className => $threshold ) { |
136 | if ( $probability >= $threshold[ 'min' ] && |
137 | $probability <= $threshold[ 'max' ] ) { |
138 | return $className; |
139 | } |
140 | } |
141 | |
142 | throw new LogicException( "Couldn't determine quality class for probability $probability" ); |
143 | } |
144 | |
145 | /** |
146 | * @param int $classId |
147 | * @return string Name of the class corresponding to the given class id |
148 | */ |
149 | private function getDraftQualityClass( $classId ) { |
150 | $modelClasses = array_flip( $this->oresModelClasses[ 'draftquality' ] ); |
151 | return $modelClasses[ $classId ]; |
152 | } |
153 | |
154 | /** |
155 | * @param string $className |
156 | * @return string Translated name of the class |
157 | */ |
158 | private function classToMessage( $className ) { |
159 | $key = self::ORES_CLASS_TO_MSG_KEY[ $className ]; |
160 | return $key ? $this->requestContext->msg( $key )->text() : ''; |
161 | } |
162 | |
163 | /** |
164 | * Fetch the 'articlequality' and 'draftquality' scores for the given page ids |
165 | * |
166 | * @param int[] $pageIds |
167 | * @return array |
168 | */ |
169 | private function fetchScores( $pageIds ) { |
170 | $pendingScore = $this->requestContext->msg( |
171 | 'pagetriage-filter-pending-ores-score' )->text(); |
172 | |
173 | $scores = []; |
174 | foreach ( $pageIds as $pageId ) { |
175 | $scores[ $pageId ] = [ |
176 | 'ores_articlequality' => $pendingScore, |
177 | 'ores_draftquality' => '', |
178 | ]; |
179 | } |
180 | |
181 | $result = $this->getORESScores( 'articlequality', $pageIds ); |
182 | foreach ( $result as $row ) { |
183 | $scores[$row->ptrp_page_id]['ores_articlequality'] = $this->classToMessage( |
184 | $this->getArticleQualityClass( $row->oresc_probability ) ); |
185 | } |
186 | |
187 | $result = $this->getORESScores( 'draftquality', $pageIds, [ 'oresc_is_predicted' => 1 ] ); |
188 | foreach ( $result as $row ) { |
189 | $scores[$row->ptrp_page_id]['ores_draftquality'] = $this->classToMessage( |
190 | $this->getDraftQualityClass( $row->oresc_class ) ); |
191 | } |
192 | |
193 | return $scores; |
194 | } |
195 | |
196 | /** |
197 | * Select ORES scores from the database. |
198 | * |
199 | * @param string $modelName |
200 | * @param int[] $pageIds |
201 | * @param array $extraConds |
202 | * @return IResultWrapper |
203 | */ |
204 | private function getORESScores( $modelName, $pageIds, $extraConds = [] ) { |
205 | $dbr = PageTriageUtil::getReplicaConnection(); |
206 | $result = $dbr->newSelectQueryBuilder() |
207 | ->select( [ |
208 | 'ptrp_page_id', |
209 | // used for articlequality |
210 | 'oresc_probability', |
211 | // used for draftquality |
212 | 'oresc_class', |
213 | ] ) |
214 | ->from( 'pagetriage_page' ) |
215 | ->join( 'page', 'page', 'ptrp_page_id=page_id' ) |
216 | ->leftJoin( 'ores_classification', 'ores_classification', 'page_latest=oresc_rev' ) |
217 | ->where( [ |
218 | 'oresc_model' => $this->modelLookup->getModelId( $modelName ), |
219 | 'ptrp_page_id' => $pageIds, |
220 | ] + $extraConds ) |
221 | ->caller( __METHOD__ ) |
222 | ->fetchResultSet(); |
223 | |
224 | return $result; |
225 | } |
226 | } |