Jump to content

User:JSherman (WMF)/revertrisk.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <nowiki>
/*
 * Add a portlet link on diff pages that:
 *   - fetches the language agnostic revert risk score
 *   - displays it in a Codex dialog
 * See: https://meta.wikimedia.org/wiki/Machine_learning_models/Proposed/Language-agnostic_revert_risk
 * @author: JSherman (WMF)
 */
( function () {
	// Define the Vue component in a variable because this is a single file
	const rootComponent = {
		name: 'DiffRevertRisk',
		// Wrap the Vue template in a js template string because this is a
		// JavaScript (.js) file, not a Vue Single File Component (.vue)
		// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
		template: `
		<cdx-dialog
			v-model:open="open"
			title="Revert Risk"
			close-button-label="Close"
			:default-action="{label: 'Close'}"
			@default="open = false"
		>
			<p>
				<h3>Revert probability</h3> {{ data }}
				<h3>Revision</h3> {{ revId }}
				<h4><cdx-icon :icon="cdxIconInfo" />Revert risk technical info</h4>
				<ul><li>
					<a href="https://meta.wikimedia.org/wiki/Machine_learning_models/Proposed/Language-agnostic_revert_risk" target="_blank" rel="noopener noreferrer">Language-agnostic revert risk model card</a>
				</li><li>
					<a href="https://api.wikimedia.org/wiki/Lift_Wing_API/Reference/Revert_risk_score_object" target="_blank" rel="noopener noreferrer">Language-agnostic revert score object</a>
				</li></ul>
			</p>
		</cdx-dialog>
		`,
		// Run in Vue 3 mode
		// See: https://www.mediawiki.org/wiki/Vue.js/Vue_3_migration
		compatConfig: {
			MODE: 3
		},
		compilerOptions: {
			whitespace: 'condense'
		},
		props: {
			revId: { type: Number, required: true },
			lang: { type: String, required: true }
		},
		data: function () {
			return { open: false, data: null };
		},
		methods: {
			// Fetch the language-agnostic revert risk for this revision
			fetchData: function () {
				fetch( 'https://api.wikimedia.org/service/lw/inference/v1/models/revertrisk-language-agnostic:predict', {
					method: 'POST',
					// eslint-disable-next-line camelcase
					body: JSON.stringify( { rev_id: this.revId, lang: this.lang } )
				} )
					.then( function ( response ) {
						return response.json();
					} )
					.then( function ( inferenceData ) {
						try {
							// Get score if it exists
							const score = ( ( ( ( inferenceData || {} ).output || {} ).probabilities || {} ).true );
							// If there is a score, display it to 2 decimal points
							// Otherwise display the 'detail' or 'error' response element
							this.data = score ? score.toFixed( 2 ) : ( ( ( inferenceData || {} ).detail || inferenceData || {} ).error );
						} catch ( error ) {
							this.data = error;
						}
					}.bind( this ) );
			}
		},
		mounted: function () {
			// Get the toolbox
			const toolboxId = 'p-tb',
				toolbox = document.getElementById( toolboxId );
			if ( !toolbox ) {
				return;
			}
			// Create the portlet link
			const portletLink = mw.util.addPortletLink(
				toolboxId,
				'#',
				'Get revert risk',
				'user-jsherman-wmf-revertrisk-fetch'
			);
			// Open the dialog and fetch the data (if necessary) on click
			portletLink.addEventListener( 'click', function ( e ) {
				// Prevent scrolling that would normally occur with '#' anchor
				e.preventDefault();
				this.open = true;
				// return early if we already have data for this revision
				if ( this.data ) {
					return;
				}
				this.fetchData();
			}.bind( this ) );
		},
		setup: function () {
			// A hacky way to use codex icons in user scripts is to just copy the svg for the icon
			// See: https://doc.wikimedia.org/codex/latest/icons/all-icons.html
			const cdxIconInfo = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><!----><g><path d="M4 10a6 6 0 1012 0 6 6 0 00-12 0m6-8a8 8 0 110 16 8 8 0 010-16m1 7v5H9V9zm0-1V6H9v2z"></path></g></svg>';
			return { cdxIconInfo };
		}
	};
	// The list of supported projects as of 2023-10-18
	function validLang( lang ) {
		if ( !lang ) {
			return false;
		}
		return [ 'aa', 'ab', 'ace', 'ady', 'af', 'ak', 'als', 'alt', 'am', 'ami', 'an', 'ang', 'anp', 'ar', 'arc', 'ary', 'arz', 'as', 'ast', 'atj', 'av', 'avk', 'awa', 'ay', 'az', 'azb', 'ba', 'ban', 'bar', 'bat-smg', 'bcl', 'be', 'be-tarask', 'be-x-old', 'bg', 'bh', 'bi', 'bjn', 'blk', 'bm', 'bn', 'bo', 'bpy', 'br', 'bs', 'bug', 'bxr', 'ca', 'cbk-zam', 'cdo', 'ce', 'ceb', 'ch', 'cho', 'chr', 'chy', 'ckb', 'co', 'cr', 'crh', 'cs', 'csb', 'cu', 'cv', 'cy', 'da', 'dag', 'de', 'din', 'diq', 'dsb', 'dty', 'dv', 'dz', 'ee', 'el', 'eml', 'en', 'eo', 'es', 'et', 'eu', 'ext', 'fa', 'fat', 'ff', 'fi', 'fiu-vro', 'fj', 'fo', 'fr', 'frp', 'frr', 'fur', 'fy', 'ga', 'gag', 'gan', 'gcr', 'gd', 'gl', 'glk', 'gn', 'gom', 'gor', 'got', 'gpe', 'gsw', 'gu', 'guc', 'gur', 'guw', 'gv', 'ha', 'hak', 'haw', 'he', 'hi', 'hif', 'ho', 'hr', 'hsb', 'ht', 'hu', 'hy', 'hyw', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'ilo', 'inh', 'io', 'is', 'it', 'iu', 'ja', 'jam', 'jbo', 'jv', 'ka', 'kaa', 'kab', 'kbd', 'kbp', 'kcg', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko', 'koi', 'kr', 'krc', 'ks', 'ksh', 'ku', 'kv', 'kw', 'ky', 'la', 'lad', 'lb', 'lbe', 'lez', 'lfn', 'lg', 'li', 'lij', 'lld', 'lmo', 'ln', 'lo', 'lrc', 'lt', 'ltg', 'lv', 'lzh', 'mad', 'mai', 'map-bms', 'mdf', 'mg', 'mh', 'mhr', 'mi', 'min', 'mk', 'ml', 'mn', 'mni', 'mnw', 'mr', 'mrj', 'ms', 'mt', 'mus', 'mwl', 'my', 'myv', 'mzn', 'na', 'nah', 'nan', 'nap', 'nds', 'nds-nl', 'ne', 'new', 'ng', 'nia', 'nl', 'nn', 'no', 'nostalgia', 'nov', 'nqo', 'nrm', 'nso', 'nv', 'ny', 'oc', 'olo', 'om', 'or', 'os', 'pa', 'pag', 'pam', 'pap', 'pcd', 'pcm', 'pdc', 'pfl', 'pi', 'pih', 'pl', 'pms', 'pnb', 'pnt', 'ps', 'pt', 'pwn', 'qu', 'rm', 'rmy', 'rn', 'ro', 'roa-rup', 'roa-tara', 'ru', 'rue', 'rup', 'rw', 'sa', 'sah', 'sat', 'sc', 'scn', 'sco', 'sd', 'se', 'sg', 'sgs', 'sh', 'shi', 'shn', 'si', 'simple', 'sk', 'skr', 'sl', 'sm', 'smn', 'sn', 'so', 'sq', 'sr', 'srn', 'ss', 'st', 'stq', 'su', 'sv', 'sw', 'szl', 'szy', 'ta', 'tay', 'tcy', 'te', 'test', 'test2', 'tet', 'tg', 'th', 'ti', 'tk', 'tl', 'tly', 'tn', 'to', 'tpi', 'tr', 'trv', 'ts', 'tt', 'tum', 'tw', 'ty', 'tyv', 'udm', 'ug', 'uk', 'ur', 'uz', 've', 'vec', 'vep', 'vi', 'vls', 'vo', 'vro', 'wa', 'war', 'wo', 'wuu', 'xal', 'xh', 'xmf', 'yi', 'yo', 'yue', 'za', 'zea', 'zh', 'zh-classical', 'zh-min-nan', 'zh-yue', 'zu' ].indexOf( lang ) !== -1;
	}
	function init() {
		// Get the diff revision
		const revId = parseInt( mw.util.getParamValue( 'oldid' ) );
		if ( !revId || Number.isNaN( revId ) ) {
			return;
		}
		// Get the page content
		const content = document.getElementById( 'mw-content-text' );
		if ( !content ) {
			return;
		}
		// Get the wiki servername language code
		const lang = mw.config.get( 'wgServerName' ).split( '.' )[ 0 ];
		if ( !validLang( lang ) ) {
			return;
		}
		// Insert a container in which we'll mount the app
		const container = document.createElement( 'div' );
		container.setAttribute( 'id', '#user-jsherman-wmf-revertrisk-dialog' );
		content.insertBefore( container, content.firstChild );
		mw.loader.using(
			[
				'vue',
				'@wikimedia/codex'
			],
			// Register codex components and mount App
			function () {
				const { CdxButton, CdxDialog, CdxIcon } = mw.loader.require( '@wikimedia/codex' );
				const { createMwApp } = mw.loader.require( 'vue' );
				createMwApp( rootComponent, { revId, lang } )
					.component( 'CdxButton', CdxButton )
					.component( 'CdxDialog', CdxDialog )
					.component( 'CdxIcon', CdxIcon )
					.mount( container );
			}
		);
	}
	mw.hook( 'wikipage.content' ).add( init );
}() );
// </nowiki>