mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/Vector.git
synced 2024-11-28 09:30:17 +00:00
Sticky header edit button A/B test bucketing
Adds behaviour for conditionally adding the edit button to the sticky-header based on A/B test bucketing. This behaviour depends on having the `$wgVectorStickyHeaderEdit` config set to true for logged-in users: $wgVectorStickyHeaderEdit = [ "logged_in" => true, "logged_out" => false ]; as well as an AB test configured with the following buckets: $wgVectorWebABTestEnrollment = [ 'name' => 'vector.sticky_header_edit', 'enabled' => true, 'buckets' => [ 'unsampled' => [ 'samplingRate' => 0 ], 'stickyHeaderEditButtonControl' => [ 'samplingRate' => 0 ], 'stickyHeaderEditButtonTreatment' => [ 'samplingRate' => 1 ] ] ]; With that config, this change hides the sticky header for all users except those in the stickyHeaderEditButtonTreatment bucket. Bug: T299959 Change-Id: If252956bc530d8ce54eeda61f42a93ffa48255cb
This commit is contained in:
parent
dafe3c8fd4
commit
42b808738a
|
@ -71,26 +71,65 @@ const main = () => {
|
||||||
allowedAction &&
|
allowedAction &&
|
||||||
'IntersectionObserver' in window;
|
'IntersectionObserver' in window;
|
||||||
|
|
||||||
const isExperimentEnabled =
|
/**
|
||||||
!!ABTestConfig.enabled &&
|
* Initialize sticky header AB tests and determine whether to show the sticky header
|
||||||
ABTestConfig.name === stickyHeader.STICKY_HEADER_EXPERIMENT_NAME &&
|
* based on which buckets the user is in.
|
||||||
!mw.user.isAnon() &&
|
*
|
||||||
isStickyHeaderAllowed;
|
* @typedef {Object} InitStickyHeaderABTests
|
||||||
|
* @property {boolean} disableEditIcons - Should the sticky header have an edit icon
|
||||||
|
* @property {boolean} showStickyHeader - Should the sticky header be shown
|
||||||
|
* @return {InitStickyHeaderABTests}
|
||||||
|
*/
|
||||||
|
function initStickyHeaderABTests() {
|
||||||
|
|
||||||
// If necessary, initialize experiment and fire the A/B test enrollment hook.
|
let show = isStickyHeaderAllowed,
|
||||||
const stickyHeaderExperiment = isExperimentEnabled &&
|
stickyHeaderExperiment,
|
||||||
initExperiment( Object.assign( {}, ABTestConfig, { token: mw.user.getId() } ) );
|
noEditIcons = true;
|
||||||
|
|
||||||
|
// Determine if user is eligible for sticky header AB test
|
||||||
|
if (
|
||||||
|
isStickyHeaderAllowed && // The sticky header can be shown on the page
|
||||||
|
ABTestConfig.enabled && // An AB test config is enabled
|
||||||
|
!mw.user.isAnon() && // The user is logged-in
|
||||||
|
( // One of the sticky-header AB tests is specified in the config
|
||||||
|
ABTestConfig.name === stickyHeader.STICKY_HEADER_EXPERIMENT_NAME ||
|
||||||
|
ABTestConfig.name === 'vector.sticky_header_edit'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// If eligible, initialize the AB test
|
||||||
|
stickyHeaderExperiment = initExperiment(
|
||||||
|
Object.assign( {}, ABTestConfig, { token: mw.user.getId() } )
|
||||||
|
);
|
||||||
|
|
||||||
|
// If running initial AB test, only show sticky header to treatment group.
|
||||||
|
if ( stickyHeaderExperiment.name === stickyHeader.STICKY_HEADER_EXPERIMENT_NAME ) {
|
||||||
|
show = stickyHeaderExperiment.isInTreatmentBucket();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If running edit-button AB test, show sticky header to all buckets
|
||||||
|
// and show edit button for treatment group
|
||||||
|
if ( stickyHeaderExperiment.name === 'vector.sticky_header_edit' ) {
|
||||||
|
show = true;
|
||||||
|
if ( stickyHeaderExperiment.isInTreatmentBucket() ) {
|
||||||
|
noEditIcons = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
showStickyHeader: show,
|
||||||
|
disableEditIcons: noEditIcons
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { showStickyHeader, disableEditIcons } = initStickyHeaderABTests();
|
||||||
|
|
||||||
// Remove class if present on the html element so that scroll padding isn't undesirably
|
// Remove class if present on the html element so that scroll padding isn't undesirably
|
||||||
// applied to users who don't experience the new treatment.
|
// applied to users who don't experience the new treatment.
|
||||||
if ( stickyHeaderExperiment && !stickyHeaderExperiment.isInTreatmentBucket() ) {
|
if ( !showStickyHeader ) {
|
||||||
document.documentElement.classList.remove( 'vector-sticky-header-enabled' );
|
document.documentElement.classList.remove( 'vector-sticky-header-enabled' );
|
||||||
}
|
}
|
||||||
|
|
||||||
const isStickyHeaderEnabled = stickyHeaderExperiment ?
|
|
||||||
stickyHeaderExperiment.isInTreatmentBucket() :
|
|
||||||
isStickyHeaderAllowed;
|
|
||||||
|
|
||||||
// Table of contents
|
// Table of contents
|
||||||
const tocElement = document.getElementById( TOC_ID );
|
const tocElement = document.getElementById( TOC_ID );
|
||||||
const tocElementLegacy = document.getElementById( TOC_ID_LEGACY );
|
const tocElementLegacy = document.getElementById( TOC_ID_LEGACY );
|
||||||
|
@ -99,25 +138,26 @@ const main = () => {
|
||||||
// Set up intersection observer for page title, used by sticky header
|
// Set up intersection observer for page title, used by sticky header
|
||||||
const observer = scrollObserver.initScrollObserver(
|
const observer = scrollObserver.initScrollObserver(
|
||||||
() => {
|
() => {
|
||||||
if ( isStickyHeaderAllowed && isStickyHeaderEnabled ) {
|
if ( isStickyHeaderAllowed && showStickyHeader ) {
|
||||||
stickyHeader.show();
|
stickyHeader.show();
|
||||||
}
|
}
|
||||||
scrollObserver.fireScrollHook( 'down', PAGE_TITLE_SCROLL_HOOK );
|
scrollObserver.fireScrollHook( 'down', PAGE_TITLE_SCROLL_HOOK );
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
if ( isStickyHeaderAllowed && isStickyHeaderEnabled ) {
|
if ( isStickyHeaderAllowed && showStickyHeader ) {
|
||||||
stickyHeader.hide();
|
stickyHeader.hide();
|
||||||
}
|
}
|
||||||
scrollObserver.fireScrollHook( 'up', PAGE_TITLE_SCROLL_HOOK );
|
scrollObserver.fireScrollHook( 'up', PAGE_TITLE_SCROLL_HOOK );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( isStickyHeaderAllowed && isStickyHeaderEnabled ) {
|
if ( isStickyHeaderAllowed && showStickyHeader ) {
|
||||||
stickyHeader.initStickyHeader( {
|
stickyHeader.initStickyHeader( {
|
||||||
header,
|
header,
|
||||||
userMenu,
|
userMenu,
|
||||||
observer,
|
observer,
|
||||||
stickyIntersection
|
stickyIntersection,
|
||||||
|
disableEditIcons
|
||||||
} );
|
} );
|
||||||
} else if ( stickyIntersection ) {
|
} else if ( stickyIntersection ) {
|
||||||
observer.observe( stickyIntersection );
|
observer.observe( stickyIntersection );
|
||||||
|
|
|
@ -212,6 +212,7 @@ function prepareEditIcons(
|
||||||
if ( !primaryEditSticky || !wikitextSticky || !protectedSticky ) {
|
if ( !primaryEditSticky || !wikitextSticky || !protectedSticky ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !primaryEdit ) {
|
if ( !primaryEdit ) {
|
||||||
removeNode( protectedSticky );
|
removeNode( protectedSticky );
|
||||||
removeNode( wikitextSticky );
|
removeNode( wikitextSticky );
|
||||||
|
@ -343,13 +344,15 @@ function prepareUserMenu( userMenu ) {
|
||||||
* @param {Element} userMenuStickyContainer
|
* @param {Element} userMenuStickyContainer
|
||||||
* @param {IntersectionObserver} stickyObserver
|
* @param {IntersectionObserver} stickyObserver
|
||||||
* @param {Element} stickyIntersection
|
* @param {Element} stickyIntersection
|
||||||
|
* @param {boolean} disableEditIcons
|
||||||
*/
|
*/
|
||||||
function makeStickyHeaderFunctional(
|
function makeStickyHeaderFunctional(
|
||||||
header,
|
header,
|
||||||
userMenu,
|
userMenu,
|
||||||
userMenuStickyContainer,
|
userMenuStickyContainer,
|
||||||
stickyObserver,
|
stickyObserver,
|
||||||
stickyIntersection
|
stickyIntersection,
|
||||||
|
disableEditIcons
|
||||||
) {
|
) {
|
||||||
const
|
const
|
||||||
userMenuStickyContainerInner = userMenuStickyContainer
|
userMenuStickyContainerInner = userMenuStickyContainer
|
||||||
|
@ -370,7 +373,9 @@ function makeStickyHeaderFunctional(
|
||||||
const ceEdit = document.querySelector( '#ca-edit a' );
|
const ceEdit = document.querySelector( '#ca-edit a' );
|
||||||
const protectedEdit = document.querySelector( '#ca-viewsource a' );
|
const protectedEdit = document.querySelector( '#ca-viewsource a' );
|
||||||
const isProtected = !!protectedEdit;
|
const isProtected = !!protectedEdit;
|
||||||
const primaryEdit = protectedEdit || ( veEdit || ceEdit );
|
// For sticky header edit A/B test, conditionally remove the edit icon by setting null.
|
||||||
|
// Otherwise, use either protected, ve, or source edit (in that order).
|
||||||
|
const primaryEdit = disableEditIcons ? null : protectedEdit || veEdit || ceEdit;
|
||||||
const secondaryEdit = veEdit ? ceEdit : null;
|
const secondaryEdit = veEdit ? ceEdit : null;
|
||||||
const disableStickyHeader = () => {
|
const disableStickyHeader = () => {
|
||||||
document.body.classList.remove( STICKY_HEADER_VISIBLE_CLASS );
|
document.body.classList.remove( STICKY_HEADER_VISIBLE_CLASS );
|
||||||
|
@ -434,6 +439,7 @@ function isAllowedAction( action ) {
|
||||||
* @property {Element} userMenu
|
* @property {Element} userMenu
|
||||||
* @property {IntersectionObserver} observer
|
* @property {IntersectionObserver} observer
|
||||||
* @property {Element} stickyIntersection
|
* @property {Element} stickyIntersection
|
||||||
|
* @property {boolean} disableEditIcons
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -449,7 +455,8 @@ function initStickyHeader( props ) {
|
||||||
props.userMenu,
|
props.userMenu,
|
||||||
userMenuStickyContainer,
|
userMenuStickyContainer,
|
||||||
props.observer,
|
props.observer,
|
||||||
props.stickyIntersection
|
props.stickyIntersection,
|
||||||
|
props.disableEditIcons
|
||||||
);
|
);
|
||||||
|
|
||||||
setupSearchIfNeeded( props.header );
|
setupSearchIfNeeded( props.header );
|
||||||
|
|
Loading…
Reference in a new issue