mediawiki-extensions-Visual.../modules/ve-mw/init/ve.init.mw.TargetEvents.js
Roan Kattouw 6d21d83244 Add instrumentation for edit schema
Move ve.track() subscriber to its own file, and have it
route mwtiming.* events (for TimingData) and mwedit.* events
(for Edit schema) differently. Most of the data population
lives in the subscriber, so actual ve.track() calls are
pretty lightweight.

Existing ve.track() calls with timing data were kept with
their names intact for backwards compatibility, but
we may eventually want to throw them out and start from scratch.

ve.init.mw.ViewPageTarget.init.js:
* Remove old track subscriber
* Track init and ready events
* Remove old ve.track( 'Edit', ... ) crap that didn't work

ve.init.mw.ViewPageTarget.js:
* Fire the saveWorkflowBegin event before the save dialog
  loads rather than after
* Remove unnecessary this.events.trackSaveError() calls:
  TargetEvents already listens to these events itself
* Remove badtoken handler because all it was was an
  unnecessary trackSaveError() call
* Add abort tracking
** Pass trackMechanism through deactivate() and cancel()

ve.init.mw.Target.js:
* Add static.integrationType to populate the 'integration'
  field in the schema

ve.init.mw.TargetEvents.js:
* Simplify onSaveError* methods away into connect bindings
* Map track topics to mwtiming.* so they can be routed separately
* Track save-related mwedit.* events

Depends on I978eda96c in WikimediaEvents

Change-Id: Iae677d9b15c71d2b18e795bd5179d11876c06abd
2014-11-21 11:59:15 -08:00

206 lines
6.4 KiB
JavaScript

/*!
* VisualEditor MediaWiki Initialization class.
*
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Initialization MediaWiki Target Analytics.
*
* @class
*
* @constructor
* @param {ve.init.mw.Target} target Target class to log events for
*/
ve.init.mw.TargetEvents = function ( target ) {
this.target = target;
this.timings = { saveRetries: 0 };
// Events
this.target.connect( this, {
saveWorkflowBegin: 'onSaveWorkflowBegin',
saveWorkflowEnd: 'onSaveWorkflowEnd',
saveInitiated: 'onSaveInitated',
save: 'onSaveComplete',
saveReview: 'onSaveReview',
saveErrorEmpty: [ 'trackSaveError', 'empty' ],
saveErrorSpamBlacklist: [ 'trackSaveError', 'spamblacklist' ],
saveErrorAbuseFilter: [ 'trackSaveError', 'abusefilter' ],
saveErrorBadToken: [ 'trackSaveError', 'badtoken' ],
saveErrorNewUser: [ 'trackSaveError', 'newuser' ],
saveErrorCaptcha: [ 'trackSaveError', 'captcha' ],
saveErrorUnknown: [ 'trackSaveError', 'unknown' ],
editConflict: [ 'trackSaveError', 'editconflict' ],
surfaceReady: 'onSurfaceReady',
showChanges: 'onShowChanges',
showChangesError: 'onShowChangesError',
noChanges: 'onNoChanges',
serializeComplete: 'onSerializeComplete',
serializeError: 'onSerializeError'
} );
};
/**
* Target specific ve.track wrapper
*
* @param {string} topic Event name
* @param {Object} data Additional data describing the event, encoded as an object
*/
ve.init.mw.TargetEvents.prototype.track = function ( topic, data ) {
data.targetName = this.target.constructor.static.name;
ve.track( 'mwtiming.' + topic, data );
if ( topic.indexOf( 'performance.system.serializeforcache' ) === 0 ) {
// HACK: track serializeForCache duration here, because there's no event for that
this.timings.serializeForCache = data.duration;
}
};
/**
* Track when user begins the save workflow
*/
ve.init.mw.TargetEvents.prototype.onSaveWorkflowBegin = function () {
this.timings.saveWorkflowBegin = ve.now();
this.track( 'behavior.lastTransactionTillSaveDialogOpen', {
duration: this.timings.saveWorkflowBegin - this.timings.lastTransaction
} );
ve.track( 'mwedit.saveIntent' );
};
/**
* Track when user ends the save workflow
*/
ve.init.mw.TargetEvents.prototype.onSaveWorkflowEnd = function () {
this.track( 'behavior.saveDialogClose', { duration: ve.now() - this.timings.saveWorkflowBegin } );
this.timings.saveWorkflowBegin = null;
};
/**
* Track when document save is initiated
*/
ve.init.mw.TargetEvents.prototype.onSaveInitated = function () {
this.timings.saveInitiated = ve.now();
this.timings.saveRetries++;
this.track( 'behavior.saveDialogOpenTillSave', {
duration: this.timings.saveInitiated - this.timings.saveWorkflowBegin
} );
ve.track( 'mwedit.saveAttempt' );
};
/**
* Track when the save is complete
* @param {string} content
* @param {string} categoriesHtml
* @param {number} newRevId
*/
ve.init.mw.TargetEvents.prototype.onSaveComplete = function ( content, categoriesHtml, newRevId ) {
this.track( 'performance.user.saveComplete', { duration: ve.now() - this.timings.saveInitiated } );
this.timings.saveRetries = 0;
ve.track( 'mwedit.saveSuccess', {
timing: ve.now() - this.timings.saveInitiated + ( this.timings.serializeForCache || 0 ),
'page.revid': newRevId
} );
};
/**
* Track a save error by type
*
* @method
* @param {string} type Text for error type
*/
ve.init.mw.TargetEvents.prototype.trackSaveError = function ( type ) {
var key,
// Maps mwtiming types to mwedit types
typeMap = {
badtoken: 'userBadToken',
newuser: 'userNewUser',
abusefilter: 'extensionAbuseFilter',
captcha: 'extensionCaptcha',
spamblacklist: 'extensionSpamBlacklist',
empty: 'responseEmpty',
unknown: 'responseUnknown',
editconflict: 'editConflict'
},
// Types that are logged as performance.user.saveError.{type}
// (for historical reasons; this sucks)
specialTypes = [ 'editconflict' ];
key = 'performance.user.saveError';
if ( specialTypes.indexOf( type ) !== -1 ) {
key += '.' + type;
}
this.track( key, {
duration: ve.now() - this.timings.saveInitiated,
retries: this.timings.saveRetries,
type: type
} );
ve.track( 'mwedit.saveFailure', {
type: typeMap[type] || 'responseUnknown',
timing: ve.now() - this.timings.saveInitiated + ( this.timings.serializeForCache || 0 )
} );
};
/**
* Record the time of the last transaction in response to a 'transact' event on the document.
*/
ve.init.mw.TargetEvents.prototype.recordLastTransactionTime = function () {
this.timings.lastTransaction = ve.now();
};
/**
* Track time elapsed from beginning of save workflow to review
*/
ve.init.mw.TargetEvents.prototype.onSaveReview = function () {
this.timings.saveReview = ve.now();
this.track( 'behavior.saveDialogOpenTillReview', {
duration: this.timings.saveReview - this.timings.saveWorkflowBegin
} );
};
ve.init.mw.TargetEvents.prototype.onSurfaceReady = function () {
this.track( 'performance.system.activation', { duration: ve.now() - this.timings.activationStart } );
this.target.surface.getModel().getDocument().connect( this, {
transact: 'recordLastTransactionTime'
} );
};
/**
* Track when the user enters the review workflow
*/
ve.init.mw.TargetEvents.prototype.onShowChanges = function () {
this.track( 'performance.user.reviewComplete', { duration: ve.now() - this.timings.saveReview } );
};
/**
* Track when the diff request fails in the review workflow
*/
ve.init.mw.TargetEvents.prototype.onShowChangesError = function () {
this.track( 'performance.user.reviewError', { duration: ve.now() - this.timings.saveReview } );
};
/**
* Track when the diff request detects no changes
*/
ve.init.mw.TargetEvents.prototype.onNoChanges = function () {
this.track( 'performance.user.reviewComplete', { duration: ve.now() - this.timings.saveReview } );
};
/**
* Track whe serilization is complete in review workflow
*/
ve.init.mw.TargetEvents.prototype.onSerializeComplete = function () {
this.track( 'performance.user.reviewComplete', { duration: ve.now() - this.timings.saveReview } );
};
/**
* Track when there is a serlization error
*/
ve.init.mw.TargetEvents.prototype.onSerializeError = function () {
if ( this.timings.saveWorkflowBegin ) {
// This function can be called by the switch to wikitext button as well, so only log
// reviewError if we actually got here from the save workflow
this.track( 'performance.user.reviewError', { duration: ve.now() - this.timings.saveReview } );
}
};