2014-10-29 01:19:52 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor MediaWiki event subscriber.
|
|
|
|
*
|
|
|
|
* Subscribes to ve.track() events and routes them to mw.track().
|
|
|
|
*
|
2020-01-08 17:13:04 +00:00
|
|
|
* @copyright 2011-2020 VisualEditor Team and others; see AUTHORS.txt
|
2014-10-29 01:19:52 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
( function () {
|
2021-10-13 12:57:45 +00:00
|
|
|
var actionPrefixMap = {
|
2019-09-10 00:32:50 +00:00
|
|
|
firstChange: 'first_change',
|
2018-10-26 20:46:36 +00:00
|
|
|
saveIntent: 'save_intent',
|
|
|
|
saveAttempt: 'save_attempt',
|
|
|
|
saveSuccess: 'save_success',
|
|
|
|
saveFailure: 'save_failure'
|
2018-11-27 17:50:01 +00:00
|
|
|
},
|
2019-11-13 19:47:36 +00:00
|
|
|
trackdebug = !!mw.Uri().query.trackdebug,
|
|
|
|
firstInitDone = false;
|
|
|
|
|
|
|
|
function getEditingSessionIdFromRequest() {
|
|
|
|
return mw.config.get( 'wgWMESchemaEditAttemptStepSessionId' ) ||
|
|
|
|
mw.Uri().query.editingStatsId;
|
|
|
|
}
|
2015-08-19 18:05:01 +00:00
|
|
|
|
2021-10-13 12:57:45 +00:00
|
|
|
var timing = {};
|
|
|
|
var editingSessionId = getEditingSessionIdFromRequest() || mw.user.generateRandomSessionId();
|
2021-10-18 23:55:52 +00:00
|
|
|
ve.init.editingSessionId = editingSessionId;
|
2014-10-29 01:19:52 +00:00
|
|
|
|
2018-12-11 16:13:06 +00:00
|
|
|
function log() {
|
|
|
|
// mw.log is a no-op unless resource loader is in debug mode, so
|
|
|
|
// this allows trackdebug to work independently (T211698)
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
console.log.apply( console, arguments );
|
|
|
|
}
|
|
|
|
|
2018-10-15 22:33:32 +00:00
|
|
|
function inSample() {
|
|
|
|
// Not using mw.eventLog.inSample() because we need to be able to pass our own editingSessionId
|
|
|
|
return mw.eventLog.randomTokenMatch(
|
2018-10-26 20:46:36 +00:00
|
|
|
1 / mw.config.get( 'wgWMESchemaEditAttemptStepSamplingRate' ),
|
2018-10-15 22:33:32 +00:00
|
|
|
editingSessionId
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-02-20 20:11:14 +00:00
|
|
|
function computeDuration( action, event, timeStamp ) {
|
|
|
|
if ( event.timing !== undefined ) {
|
|
|
|
return event.timing;
|
|
|
|
}
|
|
|
|
|
2014-10-29 01:19:52 +00:00
|
|
|
switch ( action ) {
|
|
|
|
case 'ready':
|
2015-02-20 20:11:14 +00:00
|
|
|
return timeStamp - timing.init;
|
2017-12-13 19:20:09 +00:00
|
|
|
case 'loaded':
|
|
|
|
return timeStamp - timing.init;
|
2019-09-10 00:32:50 +00:00
|
|
|
case 'firstChange':
|
|
|
|
return timeStamp - timing.ready;
|
2014-10-29 01:19:52 +00:00
|
|
|
case 'saveIntent':
|
2015-02-20 20:11:14 +00:00
|
|
|
return timeStamp - timing.ready;
|
2014-10-29 01:19:52 +00:00
|
|
|
case 'saveAttempt':
|
2015-02-20 20:11:14 +00:00
|
|
|
return timeStamp - timing.saveIntent;
|
2014-10-29 01:19:52 +00:00
|
|
|
case 'saveSuccess':
|
|
|
|
case 'saveFailure':
|
|
|
|
// HERE BE DRAGONS: the caller must compute these themselves
|
|
|
|
// for sensible results. Deliberately sabotage any attempts to
|
|
|
|
// use the default by returning -1
|
|
|
|
mw.log.warn( 've.init.mw.trackSubscriber: Do not rely on default timing value for saveSuccess/saveFailure' );
|
|
|
|
return -1;
|
|
|
|
case 'abort':
|
2015-02-20 20:11:14 +00:00
|
|
|
switch ( event.type ) {
|
2014-10-29 01:19:52 +00:00
|
|
|
case 'preinit':
|
2015-02-20 20:11:14 +00:00
|
|
|
return timeStamp - timing.init;
|
2014-10-29 01:19:52 +00:00
|
|
|
case 'nochange':
|
|
|
|
case 'switchwith':
|
|
|
|
case 'switchwithout':
|
2015-09-04 00:42:05 +00:00
|
|
|
case 'switchnochange':
|
2014-10-29 01:19:52 +00:00
|
|
|
case 'abandon':
|
2015-02-20 20:11:14 +00:00
|
|
|
return timeStamp - timing.ready;
|
2014-10-29 01:19:52 +00:00
|
|
|
case 'abandonMidsave':
|
2015-02-20 20:11:14 +00:00
|
|
|
return timeStamp - timing.saveAttempt;
|
2014-10-29 01:19:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
mw.log.warn( 've.init.mw.trackSubscriber: Unrecognized action', action );
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-10-11 16:52:14 +00:00
|
|
|
function mwEditHandler( topic, data, timeStamp ) {
|
2015-08-19 17:33:02 +00:00
|
|
|
var action = topic.split( '.' )[ 1 ],
|
2018-10-26 20:46:36 +00:00
|
|
|
actionPrefix = actionPrefixMap[ action ] || action,
|
2021-10-13 12:57:45 +00:00
|
|
|
duration = 0;
|
2015-02-20 20:11:14 +00:00
|
|
|
|
|
|
|
if ( action === 'init' ) {
|
2019-11-13 19:47:36 +00:00
|
|
|
if ( firstInitDone ) {
|
|
|
|
// Regenerate editingSessionId
|
|
|
|
editingSessionId = mw.user.generateRandomSessionId();
|
2021-10-18 23:55:52 +00:00
|
|
|
ve.init.editingSessionId = editingSessionId;
|
2019-11-13 19:47:36 +00:00
|
|
|
}
|
|
|
|
firstInitDone = true;
|
2018-10-15 22:01:26 +00:00
|
|
|
}
|
|
|
|
|
2018-11-27 17:50:01 +00:00
|
|
|
if ( !inSample() && !mw.config.get( 'wgWMESchemaEditAttemptStepOversample' ) && !trackdebug ) {
|
2018-10-15 22:01:26 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
2015-02-12 02:35:52 +00:00
|
|
|
action === 'abort' &&
|
|
|
|
( data.type === 'unknown' || data.type === 'unknown-edited' )
|
|
|
|
) {
|
|
|
|
if (
|
|
|
|
timing.saveAttempt &&
|
|
|
|
timing.saveSuccess === undefined &&
|
|
|
|
timing.saveFailure === undefined
|
|
|
|
) {
|
|
|
|
data.type = 'abandonMidsave';
|
|
|
|
} else if (
|
|
|
|
timing.init &&
|
|
|
|
timing.ready === undefined
|
|
|
|
) {
|
|
|
|
data.type = 'preinit';
|
|
|
|
} else if ( data.type === 'unknown' ) {
|
|
|
|
data.type = 'nochange';
|
|
|
|
} else {
|
|
|
|
data.type = 'abandon';
|
|
|
|
}
|
2015-02-20 20:11:14 +00:00
|
|
|
}
|
2014-12-06 19:11:02 +00:00
|
|
|
|
2018-10-26 20:46:36 +00:00
|
|
|
// Convert mode=source/visual to interface name
|
2017-12-13 17:56:35 +00:00
|
|
|
if ( data && data.mode ) {
|
2018-10-26 20:46:36 +00:00
|
|
|
// eslint-disable-next-line camelcase
|
|
|
|
data.editor_interface = data.mode === 'source' ? 'wikitext-2017' : 'visualeditor';
|
2017-12-07 11:14:00 +00:00
|
|
|
delete data.mode;
|
|
|
|
}
|
|
|
|
|
2018-08-28 14:50:11 +00:00
|
|
|
if ( !data.platform ) {
|
|
|
|
if ( ve.init && ve.init.target && ve.init.target.constructor.static.platformType ) {
|
|
|
|
data.platform = ve.init.target.constructor.static.platformType;
|
|
|
|
} else {
|
|
|
|
data.platform = 'other';
|
|
|
|
// TODO: outright abort in this case, once we think we've caught everything
|
|
|
|
mw.log.warn( 've.init.mw.trackSubscriber: no target available and no platform specified', action );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-26 20:46:36 +00:00
|
|
|
/* eslint-disable camelcase */
|
2021-11-15 17:25:51 +00:00
|
|
|
var event = ve.extendObject( {
|
2015-02-21 21:02:08 +00:00
|
|
|
version: 1,
|
|
|
|
action: action,
|
2018-10-26 20:46:36 +00:00
|
|
|
is_oversample: !inSample(),
|
|
|
|
editor_interface: 'visualeditor',
|
2015-02-21 21:02:08 +00:00
|
|
|
integration: ve.init && ve.init.target && ve.init.target.constructor.static.integrationType || 'page',
|
2018-10-26 20:46:36 +00:00
|
|
|
page_id: mw.config.get( 'wgArticleId' ),
|
|
|
|
page_title: mw.config.get( 'wgPageName' ),
|
|
|
|
page_ns: mw.config.get( 'wgNamespaceNumber' ),
|
2020-02-11 18:36:33 +00:00
|
|
|
// eslint-disable-next-line no-jquery/no-global-selector
|
2020-11-30 17:17:23 +00:00
|
|
|
revision_id: mw.config.get( 'wgRevisionId' ) || +$( 'input[name=parentRevId]' ).val() || 0,
|
2018-10-26 20:46:36 +00:00
|
|
|
editing_session_id: editingSessionId,
|
|
|
|
page_token: mw.user.getPageviewToken(),
|
|
|
|
session_token: mw.user.sessionId(),
|
|
|
|
user_id: mw.user.getId(),
|
|
|
|
user_editcount: mw.config.get( 'wgUserEditCount', 0 ),
|
|
|
|
mw_version: mw.config.get( 'wgVersion' )
|
2015-02-20 20:11:14 +00:00
|
|
|
}, data );
|
2014-10-29 01:19:52 +00:00
|
|
|
|
2015-02-20 20:11:14 +00:00
|
|
|
if ( mw.user.isAnon() ) {
|
2018-10-26 20:46:36 +00:00
|
|
|
event.user_class = 'IP';
|
2015-02-20 20:11:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-01 16:56:15 +00:00
|
|
|
// Schema's kind of a mess of special properties
|
|
|
|
if ( action === 'init' || action === 'abort' || action === 'saveFailure' ) {
|
|
|
|
event[ actionPrefix + '_type' ] = event.type;
|
|
|
|
}
|
|
|
|
if ( action === 'init' || action === 'abort' ) {
|
|
|
|
event[ actionPrefix + '_mechanism' ] = event.mechanism;
|
|
|
|
}
|
mw.trackSubscriber: Remove action.init.timing value
In Schema:Edit, all action timing durations (ready, loaded, saveAttempt etc.)
are defined as "time since the editor was initialised", which is internally
stored as the timestamp for the "init" action.
The 'init' action itself does not have a timing duratation, but the Edit schema
has a special case for it, definining it as "time since the page was loaded".
In actually, it isn't actually implemented as "time since the page loaded",
and I suspect that as such, this value is probably not used by EventLogging
consumers of the Edit schema. Or, it might be used, but doesn't represent
what the consumers think it does.
Presently, it uses the init time now() - mediaWikiLoadStart, which basically
means the time between the random point at which MediaWiki core JavaScript
finished executing which is quite variable in practice due to the race between
<script async> and browssing parsing/rendering of HTML. That is by design,
and is also why mediaWikiLoadStart is undocumented and internal, and actually
in the process of being removed.
After many iterations on this patch to try and approximate an alternative to
this undocumented variable, I came up with an alternative approach with DLynch
at the Hackathon, which is to simply not record this one timing value, but
preserve the behaviour of all the other timing values exactly as-is.
That is, keep the behaviour of storing `now()` as "init" when the editor
activates, and keep the behaviour of substracting "init" from all other action
times, but only don't report "init" itself to EventLogging (given its value
would be 0, which isn't useful).
Bug: T160315
Change-Id: I778234efe40dde8ff30333339335be1c3910a4e0
2018-04-23 18:04:25 +00:00
|
|
|
if ( action !== 'init' ) {
|
2019-04-01 16:56:15 +00:00
|
|
|
// Schema actually does have an init_timing field, but we don't want to
|
|
|
|
// store it because it's not meaningful.
|
2018-11-28 12:50:01 +00:00
|
|
|
duration = Math.round( computeDuration( action, event, timeStamp ) );
|
2018-12-03 23:33:18 +00:00
|
|
|
event[ actionPrefix + '_timing' ] = duration;
|
mw.trackSubscriber: Remove action.init.timing value
In Schema:Edit, all action timing durations (ready, loaded, saveAttempt etc.)
are defined as "time since the editor was initialised", which is internally
stored as the timestamp for the "init" action.
The 'init' action itself does not have a timing duratation, but the Edit schema
has a special case for it, definining it as "time since the page was loaded".
In actually, it isn't actually implemented as "time since the page loaded",
and I suspect that as such, this value is probably not used by EventLogging
consumers of the Edit schema. Or, it might be used, but doesn't represent
what the consumers think it does.
Presently, it uses the init time now() - mediaWikiLoadStart, which basically
means the time between the random point at which MediaWiki core JavaScript
finished executing which is quite variable in practice due to the race between
<script async> and browssing parsing/rendering of HTML. That is by design,
and is also why mediaWikiLoadStart is undocumented and internal, and actually
in the process of being removed.
After many iterations on this patch to try and approximate an alternative to
this undocumented variable, I came up with an alternative approach with DLynch
at the Hackathon, which is to simply not record this one timing value, but
preserve the behaviour of all the other timing values exactly as-is.
That is, keep the behaviour of storing `now()` as "init" when the editor
activates, and keep the behaviour of substracting "init" from all other action
times, but only don't report "init" itself to EventLogging (given its value
would be 0, which isn't useful).
Bug: T160315
Change-Id: I778234efe40dde8ff30333339335be1c3910a4e0
2018-04-23 18:04:25 +00:00
|
|
|
}
|
2019-04-01 16:56:15 +00:00
|
|
|
if ( action === 'saveFailure' ) {
|
|
|
|
event[ actionPrefix + '_message' ] = event.message;
|
|
|
|
}
|
2018-10-26 20:46:36 +00:00
|
|
|
/* eslint-enable camelcase */
|
2014-10-29 01:19:52 +00:00
|
|
|
|
2015-02-20 20:11:14 +00:00
|
|
|
// Remove renamed properties
|
|
|
|
delete event.type;
|
|
|
|
delete event.mechanism;
|
|
|
|
delete event.timing;
|
2015-03-18 22:15:54 +00:00
|
|
|
delete event.message;
|
2015-02-20 20:11:14 +00:00
|
|
|
|
2015-02-12 02:35:52 +00:00
|
|
|
if ( action === 'abort' ) {
|
|
|
|
timing = {};
|
|
|
|
} else {
|
2015-08-19 17:33:02 +00:00
|
|
|
timing[ action ] = timeStamp;
|
2015-02-12 02:35:52 +00:00
|
|
|
}
|
|
|
|
|
2021-01-29 23:24:34 +00:00
|
|
|
if ( mw.user.options.get( 'discussiontools-abtest' ) ) {
|
|
|
|
event.bucket = mw.user.options.get( 'discussiontools-abtest' );
|
|
|
|
}
|
|
|
|
|
2018-11-27 17:50:01 +00:00
|
|
|
if ( trackdebug ) {
|
2018-12-11 16:13:06 +00:00
|
|
|
log( topic, duration + 'ms', event );
|
2018-11-27 17:50:01 +00:00
|
|
|
} else {
|
|
|
|
mw.track( 'event.EditAttemptStep', event );
|
|
|
|
}
|
2018-10-11 16:52:14 +00:00
|
|
|
}
|
2014-10-29 01:19:52 +00:00
|
|
|
|
2018-10-11 16:52:14 +00:00
|
|
|
function mwTimingHandler( topic, data ) {
|
2015-02-20 20:11:14 +00:00
|
|
|
// Add type for save errors; not in the topic for stupid historical reasons
|
|
|
|
if ( topic === 'mwtiming.performance.user.saveError' ) {
|
|
|
|
topic = topic + '.' + data.type;
|
2014-10-29 01:19:52 +00:00
|
|
|
}
|
|
|
|
|
2015-02-20 20:11:14 +00:00
|
|
|
// Map mwtiming.foo --> timing.ve.foo.mobile
|
2015-04-09 12:48:16 +00:00
|
|
|
topic = topic.replace( /^mwtiming/, 'timing.ve.' + data.targetName );
|
2018-11-27 17:50:01 +00:00
|
|
|
if ( trackdebug ) {
|
2018-12-11 16:13:06 +00:00
|
|
|
log( topic, Math.round( data.duration ) + 'ms' );
|
2018-11-27 17:50:01 +00:00
|
|
|
} else {
|
|
|
|
mw.track( topic, data.duration );
|
|
|
|
}
|
2018-10-11 16:52:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function activityHandler( topic, data ) {
|
2021-10-13 12:57:45 +00:00
|
|
|
var feature = topic.split( '.' )[ 1 ];
|
2018-10-11 16:52:14 +00:00
|
|
|
|
2020-07-23 22:21:59 +00:00
|
|
|
if ( !inSample() && !mw.config.get( 'wgWMESchemaEditAttemptStepOversample' ) && !trackdebug ) {
|
2018-10-15 22:01:26 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-06 22:08:26 +00:00
|
|
|
if ( ve.init.target && (
|
|
|
|
ve.init.target.constructor.static.platformType !== 'desktop'
|
|
|
|
) ) {
|
|
|
|
// We want to log activity events when we're also logging to
|
|
|
|
// EditAttemptStep. The EAS events are only fired from DesktopArticleTarget
|
|
|
|
// in this repo. As such, we suppress this unless the current target is at
|
|
|
|
// least inheriting that. (Other tools may fire their own instances of
|
|
|
|
// those events, but probably need to reimplement this anyway for
|
|
|
|
// session-identification reasons.)
|
2019-01-31 00:53:09 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-08 17:13:01 +00:00
|
|
|
/* eslint-disable camelcase */
|
2021-10-13 12:57:45 +00:00
|
|
|
var event = {
|
2018-10-11 16:52:14 +00:00
|
|
|
feature: feature,
|
|
|
|
action: data.action,
|
2020-06-08 17:13:01 +00:00
|
|
|
editingSessionId: editingSessionId,
|
2021-02-02 12:01:00 +00:00
|
|
|
is_oversample: !inSample(),
|
2020-06-08 17:13:01 +00:00
|
|
|
user_id: mw.user.getId(),
|
|
|
|
user_editcount: mw.config.get( 'wgUserEditCount', 0 ),
|
|
|
|
editor_interface: ve.getProp( ve, 'init', 'target', 'surface', 'mode' ) === 'source' ? 'wikitext-2017' : 'visualeditor',
|
|
|
|
integration: ve.getProp( ve, 'init', 'target', 'constructor', 'static', 'integrationType' ) || 'page',
|
|
|
|
platform: ve.getProp( ve, 'init', 'target', 'constructor', 'static', 'platformType' ) || 'other'
|
2018-10-11 16:52:14 +00:00
|
|
|
};
|
2020-06-08 17:13:01 +00:00
|
|
|
/* eslint-enable camelcase */
|
2018-10-11 16:52:14 +00:00
|
|
|
|
2021-01-29 23:24:34 +00:00
|
|
|
if ( mw.user.options.get( 'discussiontools-abtest' ) ) {
|
|
|
|
event.bucket = mw.user.options.get( 'discussiontools-abtest' );
|
|
|
|
}
|
|
|
|
|
2018-11-27 17:50:01 +00:00
|
|
|
if ( trackdebug ) {
|
2018-12-11 16:13:06 +00:00
|
|
|
log( topic, event );
|
2018-11-27 17:50:01 +00:00
|
|
|
} else {
|
|
|
|
mw.track( 'event.VisualEditorFeatureUse', event );
|
|
|
|
}
|
2018-10-11 16:52:14 +00:00
|
|
|
}
|
|
|
|
|
2019-06-12 21:48:31 +00:00
|
|
|
// Only log events if the WikimediaEvents extension is installed.
|
|
|
|
// It provides variables that the above code depends on and registers the schemas.
|
|
|
|
if ( mw.config.exists( 'wgWMESchemaEditAttemptStepSamplingRate' ) ) {
|
2019-06-19 23:57:17 +00:00
|
|
|
// Ensure 'ext.eventLogging' first, it provides mw.eventLog.randomTokenMatch.
|
|
|
|
mw.loader.using( 'ext.eventLogging' ).done( function () {
|
2018-10-15 22:33:32 +00:00
|
|
|
ve.trackSubscribe( 'mwedit.', mwEditHandler );
|
|
|
|
ve.trackSubscribe( 'mwtiming.', mwTimingHandler );
|
|
|
|
ve.trackSubscribe( 'activity.', activityHandler );
|
|
|
|
} );
|
2018-10-11 16:52:14 +00:00
|
|
|
}
|
2014-10-29 01:19:52 +00:00
|
|
|
|
2016-11-12 14:43:43 +00:00
|
|
|
}() );
|