Compare commits

...

2 Commits

Author SHA1 Message Date
Jan Drewniak 4c52b69780 Sticky header AB test bucketing for 2 treatment buckets
For idwiki/viwiki, we wish to run the sticky header edit button AB
test so that treatment1 group sees the sticky header without edit
buttons, treatment2 groups sees the sticky header with edit buttons,
and the control/unsampled groups see no sticky header at all.

This patch overrides the configuration to make the sticky header
w/o edit buttons for treatment1, sticky header w/ edit buttons for
treatment2, and hides sticky header for everyone else. This depends
on a configuration with the treatment groups having "treatment1"
and "treatment2" as substrings in their bucket names.

The full configuration for idwiki/viwiki would be something like
the following:

```
$wgVectorStickyHeader = [
	"logged_in" =>  true,
	"logged_out" => false,
];

$wgVectorStickyHeaderEdit = [
		"logged_in" => true,
		"logged_out" => false,
];

$wgVectorWebABTestEnrollment = [
	"name" => "vector.sticky_header_edit",
	"enabled" => true,
	"buckets" => [
		"unsampled" => [
			"samplingRate" => 0
		],
                "noStickyHeaderControl" => [
                        "samplingRate" => 0.34
                ],
		"stickyHeaderNoEditButtonTreatment1" => [
			"samplingRate" => 0.33
		],
		"stickyHeaderEditButtonTreatment2" => [
			"samplingRate" => 0.33
		]
	],
];
```

Bug: T312573
Change-Id: I15c360fdf5393f5594602acc33b5b916e904016d
(cherry picked from commit 942cd5b0f6)
2022-08-15 21:02:37 +00:00
bwang 8772b0065e Fix grid blowout bug
Bug: T314756
Change-Id: If1a84f5c3606dab7195a9b0a320e416fba702027
2022-08-08 18:19:56 +00:00
4 changed files with 64 additions and 41 deletions

View File

@ -3,6 +3,11 @@
const EXCLUDED_BUCKET = 'unsampled';
const TREATMENT_BUCKET_SUBSTRING = 'treatment';
const WEB_AB_TEST_ENROLLMENT_HOOK = 'mediawiki.web_AB_test_enrollment';
/**
* @typedef {Function} TreatmentBucketFunction
* @param {string} [a]
* @return {boolean}
*/
/**
* @typedef {Object} WebABTest
@ -10,7 +15,7 @@ const WEB_AB_TEST_ENROLLMENT_HOOK = 'mediawiki.web_AB_test_enrollment';
* @property {function(): string} getBucket
* @property {function(string): boolean} isInBucket
* @property {function(): boolean} isInSample
* @property {function(): boolean} isInTreatmentBucket
* @property {TreatmentBucketFunction} isInTreatmentBucket
*/
/**
@ -41,12 +46,15 @@ const WEB_AB_TEST_ENROLLMENT_HOOK = 'mediawiki.web_AB_test_enrollment';
* name: 'nameOfExperiment',
* buckets: {
* unsampled: {
* samplingRate: 0.5
* samplingRate: 0.25
* },
* control: {
* samplingRate: 0.25
* },
* treatment: {
* treatment1: {
* samplingRate: 0.25
* },
* treatment2: {
* samplingRate: 0.25
* }
* },
@ -147,17 +155,18 @@ module.exports = function webABTest( props ) {
/**
* Whether or not the subject has been bucketed in a treatment bucket as
* defined by the bucket name containing the case-insensitive `treatment`
* substring (e.g. 'treatment', 'sticky-header-treatment' and
* 'stickyHeaderTreatment' are all assumed to be treatment buckets).
* defined by the bucket name containing the case-insensitive 'treatment',
* 'treatment1', or 'treatment2' substring (e.g. 'treatment', 'treatment1',
* 'sticky-header-treatment1' and 'stickyHeaderTreatment2' are all assumed
* to be treatment buckets).
*
* @param {string|null} [treatmentBucketName] lowercase name of bucket.
* @return {boolean}
*/
function isInTreatmentBucket() {
function isInTreatmentBucket( treatmentBucketName = '' ) {
const bucket = getBucket();
// eslint-disable-next-line no-restricted-syntax
return bucket.toLowerCase().includes( TREATMENT_BUCKET_SUBSTRING );
return bucket.toLowerCase().includes( `${TREATMENT_BUCKET_SUBSTRING}${treatmentBucketName}` );
}
/**

View File

@ -8,6 +8,7 @@ const
initTableOfContents = require( './tableOfContents.js' ),
deferUntilFrame = require( './deferUntilFrame.js' ),
ABTestConfig = require( /** @type {string} */ ( './config.json' ) ).wgVectorWebABTestEnrollment || {},
stickyHeaderEditIconConfig = require( /** @type {string} */ ( './config.json' ) ).wgVectorStickyHeaderEdit || true,
TOC_ID = 'mw-panel-toc',
TOC_ID_LEGACY = 'toc',
BODY_CONTENT_ID = 'bodyContent',
@ -58,7 +59,7 @@ const getHeadingIntersectionHandler = ( changeActiveSection ) => {
function initStickyHeaderABTests( abConfig, isStickyHeaderFeatureAllowed, getEnabledExperiment ) {
let show = isStickyHeaderFeatureAllowed,
stickyHeaderExperiment,
noEditIcons = true;
noEditIcons = stickyHeaderEditIconConfig;
// One of the sticky header AB tests is specified in the config
const abTestName = abConfig.name,
@ -74,16 +75,21 @@ function initStickyHeaderABTests( abConfig, isStickyHeaderFeatureAllowed, getEna
// If eligible, initialize the AB test
stickyHeaderExperiment = getEnabledExperiment( abConfig );
// If running initial AB test, only show sticky header to treatment group.
if ( abTestName === stickyHeader.STICKY_HEADER_EXPERIMENT_NAME ) {
// If running initial or edit AB test, show sticky header to treatment groups
// only. Unsampled and control buckets do not see sticky header.
if ( abTestName === stickyHeader.STICKY_HEADER_EXPERIMENT_NAME ||
abTestName === stickyHeader.STICKY_HEADER_EDIT_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 running edit-button AB test, the edit buttons in sticky header are shown
// to second treatment group only.
if ( abTestName === stickyHeader.STICKY_HEADER_EDIT_EXPERIMENT_NAME ) {
show = true;
if ( stickyHeaderExperiment.isInTreatmentBucket() ) {
if ( stickyHeaderExperiment.isInTreatmentBucket( '1' ) ) {
noEditIcons = true;
}
if ( stickyHeaderExperiment.isInTreatmentBucket( '2' ) ) {
noEditIcons = false;
}
}

View File

@ -102,7 +102,9 @@
@media ( min-width: @min-width-desktop-wide ) {
.mw-page-container-inner {
grid-template-columns: ~'284px @{width-gutter} 1fr';
/* Use of minmax is important to restrict the maximum grid column width
more information: T314756 */
grid-template-columns: ~'284px @{width-gutter} minmax(0, 1fr)';
}
}

View File

@ -62,7 +62,7 @@ describe( 'main.js', () => {
isEnabled: true,
isUserInTreatmentBucket: false,
expectedResult: {
showStickyHeader: true,
showStickyHeader: false,
disableEditIcons: true
}
},
@ -78,7 +78,7 @@ describe( 'main.js', () => {
{
abConfig: STICKY_HEADER_AB,
isEnabled: false, // sticky header unavailable
isUserInTreatmentBucket: false, // not in treament bucket
isUserInTreatmentBucket: false, // not in treatment bucket
expectedResult: {
showStickyHeader: false,
disableEditIcons: true
@ -87,7 +87,7 @@ describe( 'main.js', () => {
{
abConfig: STICKY_HEADER_AB,
isEnabled: true, // sticky header available
isUserInTreatmentBucket: false, // not in treament bucket
isUserInTreatmentBucket: false, // not in treatment bucket
expectedResult: {
showStickyHeader: false,
disableEditIcons: true
@ -96,7 +96,7 @@ describe( 'main.js', () => {
{
abConfig: STICKY_HEADER_AB,
isEnabled: false, // sticky header is not available
isUserInTreatmentBucket: true, // but the user is in the treament bucket
isUserInTreatmentBucket: true, // but the user is in the treatment bucket
expectedResult: {
showStickyHeader: false,
disableEditIcons: true
@ -129,27 +129,33 @@ describe( 'main.js', () => {
disableEditIcons: false
}
}
].forEach( ( { abConfig, isEnabled, isUserInTreatmentBucket, expectedResult } ) => {
document.documentElement.classList.add( 'vector-sticky-header-enabled' );
const result = test.initStickyHeaderABTests(
].forEach(
( {
abConfig,
// isStickyHeaderFeatureAllowed
isEnabled,
( experiment ) => ( {
name: experiment.name,
isInBucket: () => true,
isInSample: () => true,
getBucket: () => 'bucket',
isInTreatmentBucket: () => {
return isUserInTreatmentBucket;
}
} )
);
expect( result ).toMatchObject( expectedResult );
// Check that there are no side effects
expect(
document.documentElement.classList.contains( 'vector-sticky-header-enabled' )
).toBe( true );
} );
isUserInTreatmentBucket,
expectedResult
} ) => {
document.documentElement.classList.add( 'vector-sticky-header-enabled' );
const result = test.initStickyHeaderABTests(
abConfig,
// isStickyHeaderFeatureAllowed
isEnabled,
( experiment ) => ( {
name: experiment.name,
isInBucket: () => true,
isInSample: () => true,
getBucket: () => 'bucket',
isInTreatmentBucket: () => {
return isUserInTreatmentBucket;
}
} )
);
expect( result ).toMatchObject( expectedResult );
// Check that there are no side effects
expect(
document.documentElement.classList.contains( 'vector-sticky-header-enabled' )
).toBe( true );
} );
} );
} );