User:Murph9000/pagetriagestats-topreviewers.js

/** * Page triage stats, top reviewers * from API action=pagetriagestats * * Load from your common.js, for example: * * if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Blankpage' ) { *	mw.loader.load( '/w/index.php?title=User:Murph9000/pagetriagestats-topreviewers.js&action=raw&ctype=text/javascript' ); * } * * Once added to your common.js, view the dynamically generated special page at: * * https://en.wikipedia.org/wiki/Special:BlankPage?action=pagetriagestats&topreviewers=last-day * https://en.wikipedia.org/wiki/Special:BlankPage?action=pagetriagestats&topreviewers=last-week * https://en.wikipedia.org/wiki/Special:BlankPage?action=pagetriagestats&topreviewers=last-month * * Copyright © 2017 User:Murph9000 @ English Wikipedia. All rights reserved. * * Released under the Creative Commons Attribution-ShareAlike 3.0 Unported License * Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License * * Released under the Creative Commons Attribution-ShareAlike 4.0 International License * Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_4.0_International_License * * Released under the GNU Free Documentation License * Wikipedia:Text_of_the_GNU_Free_Documentation_License * * Released under the GNU General Public License, version 2 or later * https://www.gnu.org/licenses/gpl-2.0.html * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */

( function ( mw, $ ) {	'use strict';	var FILE = 'User:Murph9000/pagetriagestats-topreviewers.js';	console.info( FILE, 'startup' );

var $content, $spinner, topreviewersParam, Api, Html = mw.html, conf = mw.config.get( [			'wgAction',			'wgCanonicalSpecialPageName',			'wgNamespaceIds',			'wgPageName',		] ), messages = { 'pagetriagestats-filteredarticle': 'Filtered articles', 'pagetriagestats-filteredarticle-count': 'Count: $1', 'pagetriagestats-reviewedarticle': 'Reviewed articles', 'pagetriagestats-reviewedarticle-count': 'Count: $1', 'pagetriagestats-title': 'Page triage stats', 'pagetriagestats-topreviewers': 'Top reviewers', 'pagetriagestats-topreviewers-caption': 'Top reviewers, $1', 'pagetriagestats-topreviewers-last-day': 'last day', 'pagetriagestats-topreviewers-last-month': 'last month', 'pagetriagestats-topreviewers-last-week': 'last week', 'pagetriagestats-topreviewers-num': 'Number', 'pagetriagestats-topreviewers-total': 'Total', 'pagetriagestats-topreviewers-user': 'User', 'pagetriagestats-unreviewedarticle': 'Unreviewed articles', 'pagetriagestats-unreviewedarticle-count': 'Count: $1', 'pagetriagestats-unreviewedarticle-oldest': 'Oldest: $1', },		topreviewersValues = [ 'last-day', 'last-week', 'last-month' ],

NS_USER = conf.wgNamespaceIds.user, NS_USER_TALK = conf.wgNamespaceIds.user_talk,

contributionsTitle = 'Special:Contributions';

/**	 * Make user link (or user contributions for unregistered users) * 	 * mediawiki-1.28.2/includes/Linker.php * 	 * @param int $userId User id in database. * @param string $userName User name in database. * @param string $altUserName Text to display instead of the user name (optional) * @return string HTML fragment */	function userLink( userId, userName, altUserName ) { var page, classes = 'mw-userlink'; if ( userId === 0 ) { page = mw.Title.newFromText( contributionsTitle + '/' + userName ); // PHP does a $altUserName = IP::prettifyIP( $userName ); classes += ' mw-anonuserlink'; // Separate link class for anons (bug 43179) } else { page = mw.Title.makeTitle( NS_USER, userName ); }

// Wrap the output with tags for directionality isolation return Html.element( 'a', {			href: page.getUrl,			class: classes,			title: page.getPrefixedText		}, new Html.Raw( ' ' +			Html.escape( altUserName !== undefined ? altUserName : userName ) + ' '		) );	}

function userToolLinks( userId, userText ) { var items, page;

items = []; items.push( userTalkLink( userId, userText ) ); if ( userId ) { page = mw.Title.newFromText( contributionsTitle + '/' + userText ); items.push( Html.element( 'a', { href: page.getUrl, class: 'mw-usertoollinks-contribs', title: page.getPrefixedText }, mw.msg( 'contribslink' ) )			); }		return mw.msg( 'word-separator' ) + ' '			+ mw.message( 'parentheses',					items.join( mw.msg( 'pipe-separator' ) )				).text + ' ';	}

/**	 * @param int $userId User id in database. * @param string $userText User name in database. * @return string HTML fragment with user talk link */	function userTalkLink( userId, userText ) { var page = mw.Title.makeTitle( NS_USER_TALK, userText ), classes = 'mw-usertoollinks-talk'; return Html.element( 'a', {				href: page.getUrl,				class: classes,				title: page.getPrefixedText			}, mw.msg( 'talkpagelinktext' ) ); }

function pagetriagestatsCallback( data ) { console.log( FILE, 'pagetriagestatsCallback', data ); var stats, topreviewers, rec, total, $div, $table, $tbody;

if ( data.pagetriagestats.result !== 'success' ) { mw.log.error( FILE, 'API action=pagetriagestats did not return success' ); $content.append(				' API did not return success '			); return; }		$div = $( ' ' ).addClass( 'pagetriagestats' ); $content.append( $div );

stats = data.pagetriagestats.stats;

$div.append(			Html.element( 'dl', { class: 'stats' }, new Html.Raw(

Html.element( 'dt', { class: 'unreviewedarticle' },					mw.msg( 'pagetriagestats-unreviewedarticle' ) ) + Html.element( 'dd', { class: 'unreviewedarticle-count' },					mw.msg( 'pagetriagestats-unreviewedarticle-count', stats.unreviewedarticle.count ) ) + Html.element( 'dd', { class: 'unreviewedarticle-oldest' },					mw.msg( 'pagetriagestats-unreviewedarticle-oldest', new Date( stats.unreviewedarticle.oldest.replace( /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/, '$1-$2-$3T$4:$5:$6Z' ) )					)				) +

Html.element( 'dt', { class: 'reviewedarticle' },					mw.msg( 'pagetriagestats-reviewedarticle' ) ) + Html.element( 'dd', { class: 'reviewedarticle-count' },					mw.msg( 'pagetriagestats-reviewedarticle-count', stats.reviewedarticle.reviewed_count ) ) +

Html.element( 'dt', { class: 'filteredarticle' },					mw.msg( 'pagetriagestats-filteredarticle' ) ) + Html.element( 'dd', { class: 'filteredarticle-count' },					mw.msg( 'pagetriagestats-filteredarticle-count', stats.filteredarticle ) ) ) )		);

topreviewers = stats.topreviewers;

$div.append(			Html.element( 'h2', {}, mw.msg( 'pagetriagestats-topreviewers' ) )		);

$table = $( ' ' ).addClass( 'topreviewers wikitable' ); $table.append( Html.element( 'caption', {}, mw.msg( 'pagetriagestats-topreviewers-caption',				mw.msg( 'pagetriagestats-topreviewers-' + topreviewersParam ) ) ) );		$table.append( Html.element( 'thead', {}, new Html.Raw(			Html.element( 'tr', {}, new Html.Raw(				Html.element( 'th', { scope: 'col' }, '#' ) +				Html.element( 'th', { scope: 'col', style: 'text-align: left;' }, mw.msg( 'pagetriagestats-topreviewers-user' ) ) +				Html.element( 'th', { scope: 'col', style: 'text-align: left;' }, mw.msg( 'pagetriagestats-topreviewers-num' ) )			) )		) ) );

$tbody = $( ' ' ); total = 0; for ( var i in topreviewers ) { rec = topreviewers[ i ]; $tbody.append( Html.element( 'tr', {}, new Html.Raw(				Html.element( 'th', { scope: 'row' }, i ) +				Html.element( 'td', {}, new Html.Raw(					userLink( rec.user_id, rec.user_name ) +					userToolLinks( rec.user_id, rec.user_name )				) ) +				Html.element( 'td', {}, rec.num )			) ) ); total += Number(rec.num); }		$table.append( $tbody );

$table.append( Html.element( 'tfoot', {}, new Html.Raw(			Html.element( 'tr', {}, new Html.Raw(				//Html.element( 'th' ) +				Html.element( 'th', { scope: 'row', style: 'text-align: left;', colspan: 2 }, mw.msg( 'pagetriagestats-topreviewers-total' ) ) +				Html.element( 'th', { scope: 'col', style: 'text-align: left;' }, total )			) )		) ) );

$div.append( $table ); }

function initPage { console.log( FILE, 'ready' );

var title = mw.msg( 'pagetriagestats-title' );

$content = $( '#mw-content-text' );

if ( conf.wgCanonicalSpecialPageName === 'Blankpage' ) { document.title = mw.msg( 'pagetitle', title );

// Normal skins use #firstHeading // Mobile skin uses #section_0 for no good reason $( '#firstHeading, #section_0' ).text( title ); $content.empty; } else { $content.append( Html.element( 'h1', {}, title ) ); }

$spinner = $.createSpinner( { size: 'large', type: 'block' } ); $content.append( $spinner ); }

function loaderCallback { console.log( FILE, 'loader done' );

var maxage;

if ( conf.wgCanonicalSpecialPageName === 'Blankpage' &&			 mw.util.getParamValue( 'action' ) !== 'pagetriagestats' ) { console.info( FILE + ': rejecting, not a request for this script' ); return $.Deferred.reject; }

mw.messages.set( messages );

topreviewersParam = mw.util.getParamValue( 'topreviewers' ); if ( !topreviewersParam ||			 !topreviewersValues.includes( topreviewersParam ) ) { topreviewersParam = 'last-week'; }

/**		 * https://phabricator.wikimedia.org/diffusion/EPTR/browse/master/includes/PageTriageUtil.php * 		 * Data has server-side caching, with expiry as follows: *	last-day: 60 * 60 *	last-week: 24 * 60 * 60 *	last-month: 24 * 60 * 60 * 		 * Set our maxage based on that (but a small fraction of it, to avoid		 * doubling the delay). */		switch ( topreviewersParam ) { case 'last-day': maxage = 5 * 60; break; //case 'last-week': //case 'last-month': default: maxage = 60 * 60; }

Api = new mw.Api( {			parameters: {				formatversion: 2			}		} );

return $.when(			Api.get( { action: 'pagetriagestats', topreviewers: topreviewersParam, smaxage: maxage, maxage: maxage } ),			$.when( Api.loadMessagesIfMissing( [					'contribslink',					'pagetitle',					'parentheses',					'pipe-separator',					'talkpagelinktext',					'word-separator',				], {					smaxage: 86400,					maxage: 86400				} ), $.ready ).then( initPage )		); }

if ( conf.wgCanonicalSpecialPageName === 'Blankpage' ||		 ( conf.wgPageName === FILE && conf.wgAction === 'submit' ) ) { mw.loader.using( [			'mediawiki.Title',			'mediawiki.api',			'mediawiki.api.messages',			'mediawiki.jqueryMsg',			'mediawiki.util',			'jquery.spinner'		] ).then( loaderCallback ).done( function ( data ) {			pagetriagestatsCallback.apply( this, data );			mw.hook( 'wikipage.content' ).fire( $content );		} ).always( function {			if ( $spinner ) {				$spinner.remove;			}		} ); } } )( mediaWiki, jQuery );