mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Popups
synced 2025-01-10 04:54:33 +00:00
a702c0f499
* New action added PREVIEW_SEEN * The action will be used to signal that a page view needs to be recorded. * PREVIEW_SEEN is a delayed action which is triggered as a side-effect of the previewShow action. It is only dispatched if the user is still previewing the same card and the page related to the card has preview type `page` * The pageview changelistener is added when $wgPopupsVirtualPageViews is set to true. * The page view changelistener listens for page views and logs them using EventLogging when needed using https://meta.wikimedia.org/wiki/Schema:VirtualPageView Note: * Currently if a user has enabled the DNT header, the event will not be logged. There is ongoing discussion on the ticket and fixing this will be addressed separately. * Only title and referrer are logged in the initial version. The task demands that "namespace" is logged but this information is not provided by the summary endpoints we use so will need to be added later (if indeed needed) either via a change to that endpoint of by using JavaScript to parse the URL. Bug: T184793 Change-Id: Id1fe34e4bdada3a41f0d888a753af366d4756590
274 lines
7.7 KiB
JavaScript
274 lines
7.7 KiB
JavaScript
/**
|
|
* @module popups
|
|
*/
|
|
|
|
import * as Redux from 'redux';
|
|
import * as ReduxThunk from 'redux-thunk';
|
|
|
|
import createGateway from './gateway';
|
|
import createUserSettings from './userSettings';
|
|
import createPreviewBehavior from './previewBehavior';
|
|
import createSettingsDialogRenderer from './ui/settingsDialog';
|
|
import registerChangeListener from './changeListener';
|
|
import createIsEnabled from './isEnabled';
|
|
import { fromElement as titleFromElement } from './title';
|
|
import { init as rendererInit } from './ui/renderer';
|
|
import createExperiments from './experiments';
|
|
import { isEnabled as isStatsvEnabled } from './instrumentation/statsv';
|
|
import { isEnabled as isEventLoggingEnabled }
|
|
from './instrumentation/eventLogging';
|
|
import changeListeners from './changeListeners';
|
|
import * as actions from './actions';
|
|
import reducers from './reducers';
|
|
import createMediaWikiPopupsObject from './integrations/mwpopups';
|
|
import getUserBucket from './getUserBucket';
|
|
|
|
var mw = mediaWiki,
|
|
$ = jQuery,
|
|
|
|
BLACKLISTED_LINKS = [
|
|
'.extiw',
|
|
'.image',
|
|
'.new',
|
|
'.internal',
|
|
'.external',
|
|
'.oo-ui-buttonedElement-button',
|
|
'.cancelLink a'
|
|
];
|
|
|
|
/**
|
|
* @typedef {Function} EventTracker
|
|
*
|
|
* An analytics event tracker, i.e. `mw.track`.
|
|
*
|
|
* @param {String} topic
|
|
* @param {Object} data
|
|
*
|
|
* @global
|
|
*/
|
|
|
|
/**
|
|
* Gets the appropriate analytics event tracker for logging metrics to StatsD
|
|
* via [the "StatsD timers and counters" analytics event protocol][0].
|
|
*
|
|
* If logging metrics to StatsD is enabled for the duration of the user's
|
|
* session, then the appriopriate function is `mw.track`; otherwise it's
|
|
* `$.noop`.
|
|
*
|
|
* [0]: https://github.com/wikimedia/mediawiki-extensions-WikimediaEvents/blob/29c864a0/modules/ext.wikimediaEvents.statsd.js
|
|
*
|
|
* @param {Object} user
|
|
* @param {Object} config
|
|
* @param {Experiments} experiments
|
|
* @return {EventTracker}
|
|
*/
|
|
function getStatsvTracker( user, config, experiments ) {
|
|
return isStatsvEnabled( user, config, experiments ) ? mw.track : $.noop;
|
|
}
|
|
|
|
/**
|
|
* Gets the appropriate analytics event tracker for logging virtual page views.
|
|
*
|
|
* @param {Object} config
|
|
* @return {EventTracker}
|
|
*/
|
|
function getPageViewTracker( config ) {
|
|
return config.get( 'wgPopupsVirtualPageViews' ) ? mw.track : $.noop;
|
|
}
|
|
|
|
/**
|
|
* Gets the appropriate analytics event tracker for logging EventLogging events
|
|
* via [the "EventLogging subscriber" analytics event protocol][0].
|
|
*
|
|
* If logging EventLogging events is enabled for the duration of the user's
|
|
* session, then the appriopriate function is `mw.track`; otherwise it's
|
|
* `$.noop`.
|
|
*
|
|
* [0]: https://github.com/wikimedia/mediawiki-extensions-EventLogging/blob/d1409759/modules/ext.eventLogging.subscriber.js
|
|
*
|
|
* @param {Object} user
|
|
* @param {Object} config
|
|
* @param {String} bucket for user
|
|
* @param {Window} window
|
|
* @return {EventTracker}
|
|
*/
|
|
function getEventLoggingTracker( user, config, bucket, window ) {
|
|
return isEventLoggingEnabled(
|
|
user,
|
|
config,
|
|
bucket,
|
|
window
|
|
) ? mw.track : $.noop;
|
|
}
|
|
|
|
/**
|
|
* Returns timestamp since the beginning of the current document's origin
|
|
* as reported by `window.performance.now()`.
|
|
* @return {number|null}
|
|
*/
|
|
function getCurrentTimestamp() {
|
|
if ( window.performance && window.performance.now ) {
|
|
// return an integer; see T182000
|
|
return Math.round( window.performance.now() );
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Subscribes the registered change listeners to the
|
|
* [store](http://redux.js.org/docs/api/Store.html#store).
|
|
*
|
|
* @param {Redux.Store} store
|
|
* @param {Object} actions
|
|
* @param {UserSettings} userSettings
|
|
* @param {Function} settingsDialog
|
|
* @param {PreviewBehavior} previewBehavior
|
|
* @param {EventTracker} statsvTracker
|
|
* @param {EventTracker} eventLoggingTracker
|
|
* @param {EventTracker} pageViewTracker
|
|
* @param {Function} getCurrentTimestamp
|
|
*/
|
|
function registerChangeListeners(
|
|
store, actions, userSettings, settingsDialog, previewBehavior,
|
|
statsvTracker, eventLoggingTracker, pageViewTracker, getCurrentTimestamp
|
|
) {
|
|
registerChangeListener( store, changeListeners.footerLink( actions ) );
|
|
registerChangeListener( store, changeListeners.linkTitle() );
|
|
registerChangeListener( store, changeListeners.render( previewBehavior ) );
|
|
registerChangeListener(
|
|
store, changeListeners.statsv( actions, statsvTracker ) );
|
|
registerChangeListener(
|
|
store, changeListeners.syncUserSettings( userSettings ) );
|
|
registerChangeListener(
|
|
store, changeListeners.settings( actions, settingsDialog ) );
|
|
registerChangeListener(
|
|
store,
|
|
changeListeners.eventLogging(
|
|
actions, eventLoggingTracker, getCurrentTimestamp
|
|
) );
|
|
registerChangeListener( store,
|
|
changeListeners.pageviews( actions, pageViewTracker, window.location.href )
|
|
);
|
|
}
|
|
|
|
/*
|
|
* Initialize the application by:
|
|
* 1. Creating the state store
|
|
* 2. Binding the actions to such store
|
|
* 3. Trigger the boot action to bootstrap the system
|
|
* 4. When the page content is ready:
|
|
* - Process the eligible links for page previews
|
|
* - Initialize the renderer
|
|
* - Bind hover and click events to the eligible links to trigger actions
|
|
*/
|
|
mw.requestIdleCallback( function () {
|
|
var compose = Redux.compose,
|
|
userBucket,
|
|
store,
|
|
boundActions,
|
|
pageViewTracker,
|
|
|
|
// So-called "services".
|
|
generateToken = mw.user.generateRandomSessionId,
|
|
gateway = createGateway( mw.config ),
|
|
userSettings,
|
|
settingsDialog,
|
|
experiments,
|
|
statsvTracker,
|
|
eventLoggingTracker,
|
|
isEnabled,
|
|
previewBehavior;
|
|
|
|
userBucket = getUserBucket(
|
|
mw.experiments,
|
|
mw.config.get( 'wgPopupsAnonsExperimentalGroupSize' ),
|
|
mw.user.sessionId() );
|
|
userSettings = createUserSettings( mw.storage );
|
|
settingsDialog = createSettingsDialogRenderer();
|
|
experiments = createExperiments( mw.experiments );
|
|
statsvTracker = getStatsvTracker( mw.user, mw.config, experiments );
|
|
// Virtual page views are always tracked.
|
|
pageViewTracker = getPageViewTracker( mw.config );
|
|
eventLoggingTracker = getEventLoggingTracker(
|
|
mw.user,
|
|
mw.config,
|
|
userBucket,
|
|
window
|
|
);
|
|
|
|
isEnabled = createIsEnabled( mw.user, userSettings, mw.config, userBucket );
|
|
|
|
// If debug mode is enabled, then enable Redux DevTools.
|
|
if ( mw.config.get( 'debug' ) === true ) {
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
compose = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
|
}
|
|
|
|
store = Redux.createStore(
|
|
Redux.combineReducers( reducers ),
|
|
compose( Redux.applyMiddleware(
|
|
ReduxThunk.default
|
|
) )
|
|
);
|
|
boundActions = Redux.bindActionCreators( actions, store.dispatch );
|
|
|
|
previewBehavior = createPreviewBehavior( mw.config, mw.user, boundActions );
|
|
|
|
registerChangeListeners(
|
|
store, boundActions, userSettings, settingsDialog,
|
|
previewBehavior, statsvTracker, eventLoggingTracker,
|
|
pageViewTracker,
|
|
getCurrentTimestamp
|
|
);
|
|
|
|
boundActions.boot(
|
|
isEnabled,
|
|
mw.user,
|
|
userSettings,
|
|
generateToken,
|
|
mw.config
|
|
);
|
|
|
|
/*
|
|
* Register external interface exposing popups internals so that other
|
|
* extensions can query it (T171287)
|
|
*/
|
|
mw.popups = createMediaWikiPopupsObject( store );
|
|
|
|
mw.hook( 'wikipage.content' ).add( function ( $container ) {
|
|
var invalidLinksSelector = BLACKLISTED_LINKS.join( ', ' ),
|
|
validLinkSelector = 'a[href][title]:not(' + invalidLinksSelector + ')';
|
|
|
|
rendererInit();
|
|
|
|
$container
|
|
.on( 'mouseover keyup', validLinkSelector, function ( event ) {
|
|
var mwTitle = titleFromElement( this, mw.config );
|
|
|
|
if ( mwTitle ) {
|
|
boundActions.linkDwell(
|
|
mwTitle, this, event, gateway, generateToken
|
|
);
|
|
}
|
|
} )
|
|
.on( 'mouseout blur', validLinkSelector, function () {
|
|
var mwTitle = titleFromElement( this, mw.config );
|
|
|
|
if ( mwTitle ) {
|
|
boundActions.abandon( this );
|
|
}
|
|
} )
|
|
.on( 'click', validLinkSelector, function () {
|
|
var mwTitle = titleFromElement( this, mw.config );
|
|
|
|
if ( mwTitle ) {
|
|
boundActions.linkClick( this );
|
|
}
|
|
} );
|
|
|
|
} );
|
|
} );
|
|
|
|
window.Redux = Redux;
|
|
window.ReduxThunk = ReduxThunk;
|