mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/WikiEditor
synced 2024-11-27 17:50:44 +00:00
Schema:Edit instrumentation
Depends on Ib8612626 Bug: T88027 Change-Id: I67f1000d23cb257df29d5d4be8ae85764458e6c1
This commit is contained in:
parent
caa4ef8d08
commit
74da530f2d
|
@ -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 */
|
||||
|
||||
|
@ -135,6 +138,48 @@ class WikiEditorHooks {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
@ -161,6 +206,66 @@ class WikiEditorHooks {
|
|||
$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 @@ class WikiEditorHooks {
|
|||
$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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,10 @@ $GLOBALS['wgHooks']['ResourceLoaderGetConfigVars'][] =
|
|||
$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 @@ $GLOBALS['wgResourceModules'] += array(
|
|||
|
||||
/* 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',
|
||||
|
|
44
modules/ext.wikiEditor.init.js
Normal file
44
modules/ext.wikiEditor.init.js
Normal file
|
@ -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 ) );
|
|
@ -549,6 +549,30 @@ if ( !context || typeof context === 'undefined' ) {
|
|||
$( 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 */
|
||||
|
|
Loading…
Reference in a new issue