jenkins-bot has submitted this change and it was merged. Change subject: Schema:Edit instrumentation ......................................................................
Schema:Edit instrumentation Depends on Ib8612626 Bug: T88027 Change-Id: I67f1000d23cb257df29d5d4be8ae85764458e6c1 --- M WikiEditor.hooks.php M WikiEditor.php A modules/ext.wikiEditor.init.js M modules/jquery.wikiEditor.js 4 files changed, 277 insertions(+), 1 deletion(-) Approvals: Catrope: Looks good to me, approved jenkins-bot: Verified diff --git a/WikiEditor.hooks.php b/WikiEditor.hooks.php index 321c959..fca9406 100644 --- a/WikiEditor.hooks.php +++ b/WikiEditor.hooks.php @@ -7,6 +7,9 @@ */ class WikiEditorHooks { + // ID used for grouping entries all of a session's entries together in + // EventLogging. + private static $statsId = false; /* Protected Static Members */ @@ -136,6 +139,48 @@ } /** + * Log stuff to EventLogging's Schema:Edit - see https://meta.wikimedia.org/wiki/Schema:Edit + * If you don't have EventLogging installed, does nothing. + * + * @param string $action + * @param Article $article Which article (with full context, page, title, etc.) + * @param array $data Data to log for this action + * @return bool Whether the event was logged or not. + */ + public static function doEventLogging( $action, $article, $data = array() ) { + global $wgVersion; + if ( !class_exists( 'EventLogging' ) ) { + return false; + } + + $user = $article->getContext()->getUser(); + $page = $article->getPage(); + $title = $article->getTitle(); + + $data = array( + 'action' => $action, + 'version' => 1, + 'editor' => 'wikitext', + 'platform' => 'desktop', // FIXME + 'integration' => 'page', + 'page.length' => -1, // FIXME + 'page.id' => $page->getId(), + 'page.title' => $title->getPrefixedText(), + 'page.ns' => $title->getNamespace(), + 'page.revid' => $page->getRevision() && $page->getRevision()->getId(), + 'user.id' => $user->getId(), + 'user.editCount' => $user->getEditCount(), + 'mediawiki.version' => $wgVersion + ) + $data; + + if ( $user->isAnon() ) { + $data['user.class'] = 'IP'; + } + + return EventLogging::logEvent( 'Edit', 11448630, $data ); + } + + /** * EditPage::showEditForm:initial hook * * Adds the modules to the edit form @@ -161,6 +206,66 @@ $outputPage->addModules( $feature['modules'] ); } } + + $article = $editPage->getArticle(); + $request = $article->getContext()->getRequest(); + // Don't run this if the request was posted - we don't want to log 'init' when the + // user just pressed 'Show preview' or 'Show changes', or switched from VE keeping + // changes. + if ( class_exists( 'EventLogging' ) && !$request->wasPosted() ) { + $data = array(); + $data['editingSessionId'] = self::getEditingStatsId(); + if ( $request->getVal( 'section', false ) ) { + $data['action.init.type'] = 'section'; + } else { + $data['action.init.type'] = 'page'; + } + if ( $request->getHeader( 'Referer' ) ) { + if ( $request->getVal( 'section' ) === 'new' || !$article->exists() ) { + $data['action.init.mechanism'] = 'new'; + } else { + $data['action.init.mechanism'] = 'click'; + } + } else { + $data['action.init.mechanism'] = 'url'; + } + + self::doEventLogging( 'init', $article, $data ); + } + + return true; + } + + /** + * EditPage::showEditForm:fields hook + * + * Adds the event fields to the edit form + * + * @param EditPage $editPage the current EditPage object. + * @param OutputPage $outputPage object. + * @return bool + */ + public static function editPageShowEditFormFields( $editPage, $outputPage ) { + if ( $editPage->contentModel !== CONTENT_MODEL_WIKITEXT ) { + return true; + } + + $req = $outputPage->getContext()->getRequest(); + $editingStatsId = $req->getVal( 'editingStatsId' ); + if ( !$editingStatsId ) { + $editingStatsId = self::getEditingStatsId(); + } + $outputPage->addHTML( + Xml::element( + 'input', + array( + 'type' => 'hidden', + 'name' => 'editingStatsId', + 'id' => 'editingStatsId', + 'value' => $editingStatsId + ) + ) + ); return true; } @@ -296,4 +401,97 @@ $vars['wgWikiEditorMagicWords'] = $magicWords; } + + /** + * Adds WikiEditor JS to the output. + * + * This is attached to the MediaWiki 'BeforePageDisplay' hook. + * + * @param OutputPage $output + * @param Skin $skin + * @return boolean + */ + public static function onBeforePageDisplay( OutputPage &$output, Skin &$skin ) { + $output->addModules( array( 'ext.wikiEditor.init' ) ); + return true; + } + + /** + * Gets a 32 character alphanumeric random string to be used for stats. + * @return string + */ + private static function getEditingStatsId() { + if ( self::$statsId ) { + return self::$statsId; + } + return self::$statsId = MWCryptRand::generateHex( 32 ); + } + + /** + * This is attached to the MediaWiki 'EditPage::attemptSave' hook. + * + * @param EditPage $editPage + * @param Status $status + * @return boolean + */ + public static function editPageAttemptSave( EditPage $editPage ) { + $article = $editPage->getArticle(); + $request = $article->getContext()->getRequest(); + if ( $request->getVal( 'editingStatsId', false ) !== false ) { + self::doEventLogging( + 'saveAttempt', + $article, + array( 'editingSessionId' => $request->getVal( 'editingStatsId' ) ) + ); + } + + return true; + } + + /** + * This is attached to the MediaWiki 'EditPage::attemptSave:after' hook. + * + * @param EditPage $editPage + * @param Status $status + * @return boolean + */ + public static function editPageAttemptSaveAfter( EditPage $editPage, Status $status ) { + $article = $editPage->getArticle(); + $request = $article->getContext()->getRequest(); + if ( $request->getVal( 'editingStatsId', false ) !== false ) { + $data = array(); + $data['editingStatsId'] = $request->getVal( 'editingStatsId' ); + + if ( $status->isOK() ) { + $action = 'saveSuccess'; + } else { + $action = 'saveFailure'; + $errors = $status->getErrorsArray(); + + if ( isset( $errors[0][0] ) ) { + $data['action.saveFailure.message'] = $errors[0][0]; + } + + if ( $status->value === EditPage::AS_CONFLICT_DETECTED ) { + $data['action.saveFailure.type'] = 'editConflict'; + } else if ( $status->value === EditPage::AS_ARTICLE_WAS_DELETED ) { + $data['action.saveFailure.type'] = 'editPageDeleted'; + } else if ( isset( $errors[0][0] ) && $errors[0][0] === 'abusefilter-disallowed' ) { + $data['action.saveFailure.type'] = 'extensionAbuseFilter'; + } else if ( isset( $editPage->getArticle()->getPage()->ConfirmEdit_ActivateCaptcha ) ) { + // TODO: :( + $data['action.saveFailure.type'] = 'extensionCaptcha'; + } else if ( isset( $errors[0][0] ) && $errors[0][0] === 'spamprotectiontext' ) { + $data['action.saveFailure.type'] = 'extensionSpamBlacklist'; + } else { + // Catch everything else... We don't seem to get userBadToken or + // userNewUser through this hook. + $data['action.saveFailure.type'] = 'responseUnknown'; + } + } + self::doEventLogging( $action, $article, $data ); + } + + return true; + } } diff --git a/WikiEditor.php b/WikiEditor.php index 070a128..de63dc6 100644 --- a/WikiEditor.php +++ b/WikiEditor.php @@ -55,6 +55,10 @@ $GLOBALS['wgHooks']['ResourceLoaderTestModules'][] = 'WikiEditorHooks::resourceLoaderTestModules'; $GLOBALS['wgHooks']['MakeGlobalVariablesScript'][] = 'WikiEditorHooks::makeGlobalVariablesScript'; $GLOBALS['wgHooks']['EditPageBeforeEditToolbar'][] = 'WikiEditorHooks::EditPageBeforeEditToolbar'; +$GLOBALS['wgHooks']['EditPage::showEditForm:fields'][] = 'WikiEditorHooks::editPageShowEditFormFields'; +$GLOBALS['wgHooks']['BeforePageDisplay'][] = 'WikiEditorHooks::onBeforePageDisplay'; +$GLOBALS['wgHooks']['EditPage::attemptSave'][] = 'WikiEditorHooks::editPageAttemptSave'; +$GLOBALS['wgHooks']['EditPage::attemptSave:after'][] = 'WikiEditorHooks::editPageAttemptSaveAfter'; $wikiEditorTpl = array( 'localBasePath' => __DIR__ . '/modules', @@ -356,10 +360,16 @@ /* WikiEditor Resources */ + 'ext.wikiEditor.init' => $wikiEditorTpl + array( + 'scripts' => 'ext.wikiEditor.init.js' + ), 'ext.wikiEditor' => $wikiEditorTpl + array( 'scripts' => 'ext.wikiEditor.js', 'styles' => 'ext.wikiEditor.less', - 'dependencies' => 'jquery.wikiEditor', + 'dependencies' => array( + 'ext.wikiEditor.init', + 'jquery.wikiEditor' + ), ), 'ext.wikiEditor.dialogs' => $wikiEditorTpl + array( 'scripts' => 'ext.wikiEditor.dialogs.js', diff --git a/modules/ext.wikiEditor.init.js b/modules/ext.wikiEditor.init.js new file mode 100644 index 0000000..2ae1a41 --- /dev/null +++ b/modules/ext.wikiEditor.init.js @@ -0,0 +1,44 @@ +/*! + * WikiEditor extension initialisation + * @copyright 2015 Wikimedia Foundation and Alex Monk + * @license GPL; see COPYING + */ + +( function ( mw, $ ) { + mw.wikiEditor = { + logEditEvent: function ( action, data ) { + mw.loader.using( 'schema.Edit' ).done( function () { + data = $.extend( { + version: 1, + action: action, + editor: 'wikitext', + platform: 'desktop', // FIXME + integration: 'page', + 'page.id': mw.config.get( 'wgArticleId' ), + 'page.title': mw.config.get( 'wgPageName' ), + 'page.ns': mw.config.get( 'wgNamespaceNumber' ), + 'page.revid': mw.config.get( 'wgRevisionId' ), + 'page.length': -1, // FIXME + 'user.id': mw.user.getId(), + 'user.editCount': mw.config.get( 'wgUserEditCount', 0 ), + 'mediawiki.version': mw.config.get( 'wgVersion' ) + }, data ); + + if ( mw.user.isAnon() ) { + data['user.class'] = 'IP'; + } + + data['action.' + action + '.type'] = data.type; + data['action.' + action + '.mechanism'] = data.mechanism; + data['action.' + action + '.timing'] = data.timing === undefined ? + 0 : Math.floor( data.timing ); + // Remove renamed properties + delete data.type; + delete data.mechanism; + delete data.timing; + + mw.eventLog.logEvent( 'Edit', data ); + } ); + } + }; +}( mediaWiki, jQuery ) ); diff --git a/modules/jquery.wikiEditor.js b/modules/jquery.wikiEditor.js index 6a8d1db..b68b523 100644 --- a/modules/jquery.wikiEditor.js +++ b/modules/jquery.wikiEditor.js @@ -549,6 +549,30 @@ $( window ).resize( function ( event ) { context.fn.trigger( 'resize', event ); } ); + + mw.wikiEditor.logEditEvent( 'ready', { + editingSessionId: $( '#editform input#editingStatsId' ).val() + } ); + $( '#editform' ).submit( function () { + context.submitting = true; + } ); + this.onUnloadFallback = window.onunload; + window.onunload = function () { + var fallbackResult; + + if ( this.onUnloadFallback ) { + fallbackResult = this.onUnloadFallback(); + } + + if ( !context.submitting ) { + mw.wikiEditor.logEditEvent( 'abort', { + editingSessionId: $( '#editform input#editingStatsId' ).val(), + // TODO: abort.type + } ); + } + + return fallbackResult; + }; } /* API Execution */ -- To view, visit https://gerrit.wikimedia.org/r/191221 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I67f1000d23cb257df29d5d4be8ae85764458e6c1 Gerrit-PatchSet: 17 Gerrit-Project: mediawiki/extensions/WikiEditor Gerrit-Branch: master Gerrit-Owner: Alex Monk <kren...@gmail.com> Gerrit-Reviewer: Alex Monk <kren...@gmail.com> Gerrit-Reviewer: Catrope <roan.katt...@gmail.com> Gerrit-Reviewer: Jforrester <jforres...@wikimedia.org> Gerrit-Reviewer: Krinkle <krinklem...@gmail.com> Gerrit-Reviewer: Nuria <nu...@wikimedia.org> Gerrit-Reviewer: Ori.livneh <o...@wikimedia.org> Gerrit-Reviewer: TheDJ <hartman.w...@gmail.com> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits