Skip to content

Commit e4d4fb5

Browse files
authored
TopEdits and PagesCreated: add list of ten top wikiprojects (#521)
DEWISOTT. (Figuring out the queries took a damn long time.) On performance: same for topedits (just added an instant subquery), and we're at about +1 second for a user with 20K edits for pages. (Had to make new query.) If you've got an idea of how to integrate this into another query, feel free to. Just have bashed my head at SQL for too long today to be in a state to figure it out. Maybe will tomorrow. Didn't do for editcounter, as I don't know where we'd put it; will possibly also do tomorrow. Bug: T344464
1 parent ff5eeba commit e4d4fb5

File tree

14 files changed

+424
-103
lines changed

14 files changed

+424
-103
lines changed

assets/css/application.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,3 +730,12 @@ a.help-icon {
730730
.reverted-edit {
731731
background: #fcf8e3 !important;
732732
}
733+
734+
.side-to-side > div {
735+
display: inline-block;
736+
vertical-align: top;
737+
}
738+
739+
.side-to-side {
740+
clear: both;
741+
}

i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@
514514
"wiki": "Wiki",
515515
"wiki-set-change": "Wiki set change",
516516
"wikiproject": "WikiProject",
517+
"top-wikiprojects": "Top WikiProjects",
517518
"wikitext": "Wikitext",
518519
"with-summary": "With summary",
519520
"words": "Words",

i18n/qqq.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@
533533
"wiki": "Short label for Wiki\n{{Identical|Wiki}}",
534534
"wiki-set-change": "Label for MediaWiki action done by stewards that changes the wiki sets. See [[meta:Special:WikiSets]] for possible translations.",
535535
"wikiproject": "{{Identical|WikiProject}}",
536+
"top-wikiprojects": "Caption for table of the top WikiProjects (by number of edits or pages). This message should be short.",
536537
"wikitext": "Name of the markup language used on wiki pages. This message is shown as one of the download options.\n{{Identical|Wikitext}}",
537538
"with-summary": "Chart label for edits with summary. This message should be short",
538539
"words": "Label for the number of words that are in an article.\n{{Identical|Word}}",
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/entrypoints.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"/build/app.65582b0e.js"
88
],
99
"css": [
10-
"/build/app.da10c09b.css"
10+
"/build/app.b59bcd25.css"
1111
]
1212
}
1313
}

public/build/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"build/app.css": "/build/app.da10c09b.css",
2+
"build/app.css": "/build/app.b59bcd25.css",
33
"build/app.js": "/build/app.65582b0e.js",
44
"build/runtime.js": "/build/runtime.c217f8c4.js",
55
"build/95.7a87ed1a.js": "/build/95.7a87ed1a.js",

src/Model/Pages.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,42 @@ public function getAssessmentCounts(): array
301301
return $counts;
302302
}
303303

304+
/**
305+
* Get the number of pages the user created by WikiProject.
306+
* @return array Keys are the WikiProject name, values are the counts.
307+
*/
308+
public function getWikiprojectCounts(): array
309+
{
310+
if ($this->getNumPages() > $this->resultsPerPage()) {
311+
$counts = $this->repository->getWikiprojectCounts(
312+
$this->project,
313+
$this->user,
314+
$this->namespace,
315+
$this->redirects,
316+
$this->start,
317+
$this->end,
318+
);
319+
} else {
320+
$counts_tmp = [];
321+
foreach ($this->pages as $nsPages) {
322+
foreach ($nsPages as $page) {
323+
foreach ($page['assessment']['projects'] as $project) {
324+
$counts_tmp[$project] ??= 0;
325+
$counts_tmp[$project]++;
326+
}
327+
}
328+
}
329+
arsort($counts_tmp);
330+
$counts_tmp = array_slice($counts_tmp, 0, 10);
331+
$counts = [];
332+
foreach ($counts_tmp as $project => $count) {
333+
$counts[] = [ "pap_project_title" => $project, "count" => $count ];
334+
}
335+
}
336+
337+
return $counts;
338+
}
339+
304340
/**
305341
* Number of results to show, depending on the namespace.
306342
* @param bool $all Whether to get *all* results. This should only be used for
@@ -452,6 +488,7 @@ private function formatPages(array $pages): array
452488
->getBadgeURL($row['pa_class'] ?: 'Unknown'),
453489
'color' => $attrs['color'],
454490
'category' => $attrs['category'],
491+
'projects' => json_decode($row['pap_project_title'] ?? '[]'),
455492
];
456493
}
457494

src/Model/TopEdits.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,45 @@ public function getNumTopEdits(): int
155155
return count($this->topEdits);
156156
}
157157

158+
/**
159+
* Get the WikiProject totals.
160+
* @param int Namespace ID.
161+
* @return string[]|int
162+
*/
163+
public function getProjectTotals(int $ns) : array
164+
{
165+
if ($this->getNumPagesAnyNamespace($ns) > $this->limit) {
166+
$projectTotals = $this->repository->getProjectTotals(
167+
$this->project,
168+
$this->user,
169+
$ns,
170+
$this->start,
171+
$this->end
172+
);
173+
} else {
174+
$counts_tmp = [];
175+
// List of pages for this namespace
176+
$rows = $this->topEdits[$ns];
177+
foreach ($rows as $row) {
178+
$num = $row["count"];
179+
// May be null or nonexistent for assessment-less pages
180+
$titles = $row["pap_project_title"] ?? "{}";
181+
// Had to use json to pass multiple values in SQL select
182+
foreach (json_decode($titles) as $projectName) {
183+
$counts_tmp[$projectName] ??= 0;
184+
$counts_tmp[$projectName] += $num;
185+
}
186+
}
187+
arsort($counts_tmp);
188+
$counts_tmp = array_slice($projectTotals, 0, 10);
189+
$projectTotals = [];
190+
foreach ($counts_tmp as $project => $count) {
191+
$counts[] = [ "pap_project_title" => $project, "count" => $count ];
192+
}
193+
}
194+
return $projectTotals;
195+
}
196+
158197
/**
159198
* Get the average time between edits (in days).
160199
* @return float

src/Repository/PagesRepository.php

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,22 @@ public function getPagesCreated(
118118
$hasPageAssessments = $this->isWMF && $project->hasPageAssessments($namespace);
119119
if ($hasPageAssessments) {
120120
$pageAssessmentsTable = $project->getTableName('page_assessments');
121-
$conditions['paSelects'] = ", (SELECT pa_class
122-
FROM $pageAssessmentsTable
123-
WHERE rev_page = pa_page_id
124-
AND pa_class != ''
125-
LIMIT 1
126-
) AS pa_class";
127-
$conditions['paSelectsArchive'] = ', NULL AS pa_class';
121+
$paProjectsTable = $project->getTableName('page_assessments_projects');
122+
$conditions['paSelects'] = ",
123+
(SELECT pa_class
124+
FROM $pageAssessmentsTable
125+
WHERE rev_page = pa_page_id
126+
AND pa_class != ''
127+
LIMIT 1
128+
) AS pa_class,
129+
(SELECT JSON_ARRAYAGG(pap_project_title)
130+
FROM $pageAssessmentsTable
131+
JOIN $paProjectsTable
132+
ON pa_project_id = pap_project_id
133+
WHERE pa_page_id = page_id
134+
) AS pap_project_title";
135+
$conditions['paSelectsArchive'] = ', NULL AS pa_class, NULL as pap_project_title';
136+
$conditions['revPageGroupBy'] = 'GROUP BY rev_page';
128137
}
129138

130139
$wasRedirect = $this->getWasRedirectClause($redirects, $deleted);
@@ -350,6 +359,62 @@ public function getAssessmentCounts(
350359
return $this->setCache($cacheKey, $assessments);
351360
}
352361

362+
/**
363+
* Get the number of pages the user created by WikiProject.
364+
* Max 10 projects.
365+
* @param Project $project
366+
* @param User $user
367+
* @param int|string $namespace
368+
* @param string $redirects One of the Pages::REDIR_ constants.
369+
* @param int|false $start Start date as Unix timestamp.
370+
* @param int|false $end End date as Unix timestamp.
371+
* @return array Each element is an array with keys pap_project_title and count.
372+
*/
373+
public function getWikiprojectCounts(
374+
Project $project,
375+
User $user,
376+
$namespace,
377+
string $redirects,
378+
$start = false,
379+
$end = false
380+
): array {
381+
$cacheKey = $this->getCacheKey(func_get_args(), 'user_pages_created_wikiprojects');
382+
if ($this->cache->hasItem($cacheKey)) {
383+
return $this->cache->getItem($cacheKey)->get();
384+
}
385+
386+
$pageTable = $project->getTableName('page');
387+
$revisionTable = $project->getTableName('revision');
388+
$pageAssessmentsTable = $project->getTableName('page_assessments');
389+
$paProjectsTable = $project->getTableName('page_assessments_projects');
390+
391+
$conditions = array_merge(
392+
$this->getNamespaceRedirectAndDeletedPagesConditions($namespace, $redirects),
393+
$this->getUserConditions('' !== $start.$end)
394+
);
395+
$revDateConditions = $this->getDateConditions($start, $end);
396+
397+
$sql = "SELECT pap_project_title, count(pap_project_title) as `count`
398+
FROM $pageTable
399+
LEFT JOIN $revisionTable ON page_id = rev_page
400+
JOIN $pageAssessmentsTable ON page_id = pa_page_id
401+
JOIN $paProjectsTable ON pa_project_id = pap_project_id
402+
WHERE ".$conditions['whereRev']."
403+
AND rev_parent_id = '0'".
404+
$conditions['namespaceRev'].
405+
$conditions['redirects'].
406+
$revDateConditions."
407+
GROUP BY pap_project_title
408+
ORDER BY `count` DESC
409+
LIMIT 10";
410+
411+
$totals = $this->executeQuery($sql, $project, $user, $namespace)
412+
->fetchAllAssociative();
413+
414+
// Cache and return.
415+
return $this->setCache($cacheKey, $totals);
416+
}
417+
353418
/**
354419
* Fetch the closest 'delete' event as of the time of the given $offset.
355420
*

src/Repository/TopEditsRepository.php

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ public function getTopEditsNamespace(
122122
LIMIT 1
123123
) AS pa_class"
124124
: '';
125+
$paProjectsTable = $project->getTableName('page_assessments_projects');
126+
$paProjectsSelect = $hasPageAssessments
127+
? ", (
128+
SELECT JSON_ARRAYAGG(pap_project_title)
129+
FROM $paTable
130+
JOIN $paProjectsTable
131+
ON pa_project_id = pap_project_id
132+
WHERE pa_page_id = page_id
133+
) AS pap_project_title"
134+
: '';
125135

126136
$ipcJoin = '';
127137
$whereClause = 'rev_actor = :actorId';
@@ -137,7 +147,9 @@ public function getTopEditsNamespace(
137147
$sql = "SELECT page_namespace AS `namespace`, page_title,
138148
page_is_redirect AS `redirect`, COUNT(page_title) AS `count`
139149
$paSelect
150+
$paProjectsSelect
140151
FROM $pageTable
152+
141153
JOIN $revisionTable ON page_id = rev_page
142154
$ipcJoin
143155
WHERE $whereClause
@@ -200,6 +212,66 @@ public function countEditsNamespace(Project $project, User $user, $namespace, $s
200212
return $this->setCache($cacheKey, $resultQuery->fetchOne());
201213
}
202214

215+
/**
216+
* Get the 10 Wikiprojects within which the user has the most edits.
217+
* @param Project $project
218+
* @param User $user
219+
* @param int $ns
220+
* @param int|false $start
221+
* @param int|false $end
222+
*/
223+
public function getProjectTotals(
224+
Project $project,
225+
User $user,
226+
int $ns,
227+
$start = false,
228+
$end = false
229+
): array {
230+
$cacheKey = $this->getCacheKey(func_get_args(), 'top_edits_wikiprojects');
231+
if ($this->cache->hasItem($cacheKey)) {
232+
return $this->cache->getItem($cacheKey)->get();
233+
}
234+
235+
$revDateConditions = $this->getDateConditions($start, $end);
236+
$pageTable = $project->getTableName('page');
237+
$revisionTable = $project->getTableName('revision');
238+
$pageAssessmentsTable = $project->getTableName('page_assessments');
239+
$paProjectsTable = $project->getTableName('page_assessments_projects');
240+
241+
$ipcJoin = '';
242+
$whereClause = 'rev_actor = :actorId';
243+
$params = [];
244+
if ($user->isIpRange()) {
245+
$ipcTable = $project->getTableName('ip_changes');
246+
$ipcJoin = "JOIN $ipcTable ON rev_id = ipc_rev_id";
247+
$whereClause = 'ipc_hex BETWEEN :startIp AND :endIp';
248+
[$params['startIp'], $params['endIp']] = IPUtils::parseRange($user->getUsername());
249+
}
250+
251+
$sql = "SELECT pap_project_title, SUM(`edit_count`) AS `count`
252+
FROM (
253+
SELECT page_id, COUNT(page_id) AS `edit_count`
254+
FROM $revisionTable
255+
$ipcJoin
256+
JOIN $pageTable ON page_id = rev_page
257+
WHERE $whereClause
258+
AND page_namespace = :namespace
259+
$revDateConditions
260+
GROUP BY page_id
261+
) a
262+
JOIN $pageAssessmentsTable ON pa_page_id = page_id
263+
JOIN $paProjectsTable ON pa_project_id = pap_project_id
264+
GROUP BY pap_project_title
265+
ORDER BY `count` DESC
266+
LIMIT 10";
267+
268+
$totals = $this->executeQuery($sql, $project, $user, $ns)
269+
->fetchAllAssociative();
270+
271+
// Cache and return.
272+
return $this->setCache($cacheKey, $totals);
273+
}
274+
203275
/**
204276
* Get the top edits by a user across all namespaces.
205277
* @param Project $project
@@ -236,6 +308,17 @@ public function getTopEditsAllNamespaces(
236308
LIMIT 1
237309
) AS pa_class"
238310
: '';
311+
$paProjectsTable = $project->getTableName('page_assessments_projects');
312+
$paProjectsSelect = $hasPageAssessments
313+
? ", (
314+
SELECT JSON_ARRAYAGG(pap_project_title)
315+
FROM $pageAssessmentsTable
316+
JOIN $paProjectsTable
317+
ON pa_project_id = pap_project_id
318+
WHERE pa_page_id = e.page_id
319+
) AS pap_project_title"
320+
: '';
321+
239322

240323
$ipcJoin = '';
241324
$whereClause = 'rev_actor = :actorId';
@@ -248,7 +331,7 @@ public function getTopEditsAllNamespaces(
248331
}
249332

250333
$sql = "SELECT c.page_namespace AS `namespace`, e.page_title,
251-
c.page_is_redirect AS `redirect`, c.count $paSelect
334+
c.page_is_redirect AS `redirect`, c.count $paSelect $paProjectsSelect
252335
FROM
253336
(
254337
SELECT b.page_namespace, b.page_is_redirect, b.rev_page, b.count

0 commit comments

Comments
 (0)