Page MenuHomePhabricator

RFC: Standard method for feature-management in skins/extensions
Open, Stalled, HighPublic

Description

  • Affected components: MediaWiki core, skins, and extensions.
  • Engineer for initial implementation: Readers Web team.
  • Code steward: Readers Web team.

Motivation

Preamble

As part of the Wikimedia Foundation’s Medium-term plan, the Readers Web team will be making significant improvements to the desktop experience of Wikimedia projects.

The Desktop Improvements project is divided into a set of many stages. Each of those followed by a rollout to testing wikis. We are building a new look&feel for Wikimedia projects. One of our requirements is to keep the old look&feel for people who do not want to use Desktop improvements while we still work on it.
Once the project is done and we solve all problems with gadgets/users scripts/templates. We plan to migrate all the features we develop into default experience. Before that happens, everything has to be feature-flagged and visible only on some testing wikis. Additionally, we would like to provide an opt-out on testing wikis and opt-in on all wikis.

The proposal here is to agree on two things:

  • the way on how to manage new, significant changes that are feature-flagged. We should be able to quickly A/B test those changes/allow the user to opt-into "improved mode" to see new UI and later promote those to as default experience. We would like to use Feature Management from Mobile Frontend. It fits our needs nicely and solves most of the problems stated in this RFC.
  • where to keep the new code that could help us tackle this problem
Problem statement

Currently, there is no feature management in MediaWiki Core. The code has to handle its visibility state by using $config object and some additional checks that may have to be hardcoded in multiple places. It is not a problem when we want to handle only a single feature. The problem arises when we want to:

  • disable/enable multiple features at once
  • do incremental rollouts where feature depend upon each other and only one feature is available for public
  • promote some parts of functionality into default experience but still keeping rest disabled
  • A/B test some features for a short time
  • allow one feature to be available only if another feature is available
  • supporting dev environments and 3rd parties in a proper manner (dev should be experimental, 3rd party gets finished features)
  • handling configs and opt-in/opt-outs can become a complex set of if statements in many places of the codebase.

Most of this stuff can be hardcoded, but when we want to do similar things for different features, we find ourselves writing the same code over and over again. For example, the A/B testing procedure looks like that:

  • a) write and deploy some code to support A/B test
  • b) SWAT the config to enable A/B test
  • c) disable the A/B test code by SWAT
  • d) remove the A/B testing code
Requirements

The Feature Management (or anything that we come up with) has to

  • support feature flagging some parts of the app
  • provide a way to bundle features into packages user can opt into
  • provide an easy way to depend not only config flags but also on the ContextSource (namespace/logged_in users etc.)
  • provide a unified way to A/B test features on the backend side
  • supporting default configs for dev/3rd party/prod environments
  • provide an easy way to promote things to stable without us having to edit code
  • automated testing can be run against various combinations of flags
  • if anonymous users are affected, cache must vary for each set of flags

After a quick chat with other teams, it looks like there is a need for such system as many projects involve complex logic for features visibility.


Exploration

Existing solution

The Feature Management implemented in MobileFrontend already tackles some of those points. We would like to promote Feature Management so it can is widely used by different extensions/teams.
The idea behind Feature Management is that we have 2 main components - a list of all available features and a list of all available modes. On top of that, we have a manager that has access to all available features, mode, and config.
In configs, we store configuration per each feature. Each feature config contains a list of modes it's enabled/disabled in, e.g.:

"MinervaAdvancedMainMenu": {
    "value": {
        "base": false,
        "beta": false,
        "amc": true,
    }
},

or

"MinervaHistoryInPageActions": {
    "value": {
        "base": false,
        "beta": false,
        "loggedin": true
    }
},

The attached config means that AdvancedMainMenu feature is available only in the AMC mode, but not in beta nor default mode, and the HistoryInPageActions is available only for logged in users. In the system
we only call $featuresManager->isAvailableForCurrentUser( 'AdvancedMainMenu' ) and everything happens under the hood. When we want to A/B test some feature, we need to provide an A/B test mode, sth like:

"MinervaHistoryInPageActions": {
    "value": {
        "base": false,
        "beta": false,
        "experiment_vector_improvements": true
    }
},

and then the experiment mode would bucket the user and based on some criteria return if feature is available or not (this part is not implemented yet).

Modes are specified in code ( implementations of IUserMode). Currently, we have StableMode, BetaMode, AMCMode and LoggedInMode. Moving features between modes are as simple as just changing the configs. No code is required. Adding new mode narrows down to creating a new class that implements IUserMode and registering it in ServiceWirings file.

You can see the existing solution here: https://github.com/wikimedia/mediawiki-extensions-MobileFrontend/tree/c30112068cc95c4523abd472ee0a644aecdbb9a9/includes/features

The engineers of the Readers Web team came up with five proposals. Each option has many pros and cons.

Proposal 1: No framework (config-based conditionals)

Instead of implementing the Feature Management system, we can utilize what we currently have in MediaWiki. Config arrays have some merging strategies that could help us solve some such problem.

Pros:

  • nothing to write, we already have config objects
  • the best code is no code

Cons:

  • bit messy, need to remember many config options, might have to write functions that do checks across multiple config flags and/or user options
  • need to write new code every time we want to change feature visibility (A/B test it, enable for logged in, enable as default experience)
  • easy to make mistakes
  • difficult to unit test as each feature will have it's own logic when it's visible/not
  • merging strategies in config files can be difficult to understand
Proposal 2: Use or extend BetaFeatures

The BetaFeatures extension is designed to serve such a purpose. Not all things is supported by BetaFeatures, and we would have to implement Feature Sets and handle anonymous users (as currently, BetaFeatures can handle only logged-in users). Another problem we face is that the core functionalities of Vector will depend upon an extension which, in our opinion, is not the optimal solution.

Pros:

  • it feels like BetaFeatures is a great place for such code

Cons:

  • another dependency making Vector and other extensions we're going to touch depend upon BetaFeatures.
  • it needs lots of work. Currently, BetaFeatures supports only opt-in for a single feature. There are no feature sets, no modes.
  • it feels like a/b testing doesn't fit into BetaFeatures.
  • supporting instances without BetaFeatures extension is tricky. Desktop Improvements outcome is going to be default experience. Tangling Desktop Improvements with an extension that might not be available on 3rd party makes it bit more complex
  • BetaFeatures supports only logged-in users, we want to enable some features for anon users
Proposal 2: Local to Vector

Most of our work circles around Vector skin, therefore it makes sense to implement the code supporting our changes in Vector. There are many other extensions (like UniversalLanguageSelector) that inject UI elements to Vector, and we would like to amend those. It means that those extensions will have to be aware of special Vector handling (the improvements mode). Therefore it creates a dependency upon Vector which is not an optimal solution.

Pros:

  • fast and easy
  • we don't have to worry about different parts of the ecosystem

Cons:

  • code duplication with MobileFrontend
  • there might be problems when it comes to other extensions, as other extensions might depend upon Vector (by using Vector Feature Management classes/interfaces)
  • we will provide lots of useful code that most probably won't be used anywhere else
  • code won't be available for other teams to use it
Proposal 3: Implement in MediaWiki core

The Vector skin is the default skin shipped with MediaWiki core. We would like to minimize Vector dependencies to a minimum. Therefore if we want to create a reusable solution, it should go into something that Vector already depends.
Feature Management code feels like a bit more complex Config structure that additionally performs various checks before it returns true or false.

Pros:

  • Feature Management can be unified and used across Vector, Minerva, MobileFrontend, and other extensions/skins. Our work will touch not only Vector but also other extensions.
  • can lead to simplifying extensions/skins code as most of the responsibility will be held by Core.
  • later could be used by different projects. Therefore code is expected to evolve
  • it feels like this should be an integral part of MediaWiki
  • no external dependencies skins/extensions

Cons:

  • longer review cycle
  • what to do with BetaFeatures extension - maybe FeatureManagement could use BetaFeatures as storage?
Proposal 4: Implement as standalone library (Composer)

We also explored an option to create an external library and load it with the composer in all extensions/skins we're going to amend. Sadly this option raised lots of unknowns, and it is an area we could explore more.

Pros:

  • Easy to start and start writing code
  • clear separation between FeatureManagement library and code that uses it

Cons:

  • can be confusing to bind with MediaWiki ecosystem (Hooks/ServiceWirings etc.)
  • at the end it cannot be a standalone library as it needs to utilize MediaWiki ecosystem

Author recommendation

Readers Web had a long discussion on how to tackle this problem, and we would like to head with Implement it in core (Proposal 3). We already tried to support Feature Management in Minerva, which can run without MobileFrontend ( in desktop mode), and we encountered many small bumps on the way. The Feature Management feels like something Core should support/provide, and Beta Features extensions feel like a "storage" space for user opt-in/opt-outs.

The current solution is an adaptation of previous Stable/Beta mode, and it has some quirks which we need to solve before porting it into Core. Also, we learned we could not depend upon RequestContext in ServiceWirings file, and the system has to pass the current IContextSource when performing feature checks. With those lessons learned, we genuinely think we can provide a fantastic piece into the core MediaWiki that could be used by different teams across multiple projects.

Before we start doing that, we would like to reach a wider group and ask for comments and suggestions.

Event Timeline

@polishdeveloper Is it implied that the feature in question would be (mainly) developed and maintained long-term by Reading-Web? If not, is there roadmap/stewardship in place with another team?

I'm asking to determine how to priotise this as we generally prefer not to invest more heavily in the discussion until resourcing etc has been determined. Thanks!

@Krinkle I don't think there is a roadmap/stewardship in place for this project. We found a need for such solution, I also did a quick research and looks like other teams are facing similar problems as we start to feature flag and A/B test many things. This is an idea to centralize the common requirement we, Readers Web have (maintaining multiple modes) and make it handy for other teams. I can definitely help with maintaining such project in my volunteer time.

@Krinkle I'd like to follow-up on what I wrote earlier. I checked with Readers Web, and we agreed that we currently maintain the Feature Management in MobileFrontend. We're going to do something similar in Vector, therefore we will have to maintain it also in Vector. Therefore there is no difference for us between maintaining it in MobileFrontend/Vector or maintaining it in the MediaWiki Core.

TL;DR; - Readers Web will maintain the Features Management in core.

Adding the priority from the team's side

Krinkle renamed this task from [RFC] Port MobileFrontend Feature Management into core to RFC: Standard method for feature-management in skins/extensions.Feb 19 2020, 10:06 PM
Krinkle updated the task description. (Show Details)
WDoranWMF subscribed.

@kchapman Should we track this under an initiative?

Sounds great, a feature flag system in core would be quite useful.

One thing we needed several times in GrowthExperiments is a hidden user option shadowing a site setting (so that community members involved in development can test the feature before it is publicly enabled). I guess that would be a UserMode here?

@Tgr yes, so in this idea you would have - the UserMode called ( GrowthCommunityMemeber ).
Then you would have multiple features, that are not available to any mode, only to growthCommunityMemeber. Once User opts into growthCommunityMember (or someone opt's that person) user will get all features available on that mode.

We started some work on this project, to have some working solution till this RFC is closed. Please check T244481: Provide basic FeatureManagement in Vector codebase - and the gerrit patch: https://gerrit.wikimedia.org/r/#/c/mediawiki/skins/Vector/+/572323/
Instead of UserMode I started using Set, because some modes are not typical user modes - for example an A/B test or an experiment. It's not a UserMode - it's more a set of features that is available in given context.

So, to your comment, you would have to define:

$featureManager->registerSet( new UserSelectableSet( 'GECommunityMemeberSet', {OPTION_NAME});

or just define a GECommunityMemberSet that extends UserSelectableSet and in __construct() it passes the name, and the option_name. The name could be defined as const so it's easily accessible in the codebase.

How to use it

You must define a feature (so system knows there is a feature with given name). This can be done by $featureManager->registerFeature();
Then once feature is registered - feature is there but is not available to anyone. To make it available only for a community member you would define the feature config as:

"SomeAmazingFeature" => [
  "GECommunityMemeberSet" => true
]

which means that feature is available only for people who opted into GECommunityMemeberSet. Once you decide to make this feature available to everyone you just change the config to

SomeAmazingFeature => [
  "loggedin" => true
]

and immediately everyone logged in will get that feature, no code changes are required.

Now, how to access those things, in code you would do:

a) to check if user has a feature:

MediaWikiServices::getInstance()->getService('FeatureManager')->hasFeature( $FEATURE_NAME, $contextSource);

b) to check if user opted into mode/set

MediaWikiServices::getInstance()->getService('FeatureManager')->isSetActive( $SET_NAME, $contextSource);

And the last bit, we're still thinking on how to name the UserMode, I came up with the name Set, as UserMode was a bit too narrow. We're open for ideas for a better name.

Would a meeting with Tech Com or a public IRC meeting be useful at this point?

I'm more than happy to join any meetings, explain the idea behind Feature Management and try to answer all questions.

WMDE-leszek rescinded a token.
WMDE-leszek awarded a token.
WMDE-leszek subscribed.
Krinkle updated the task description. (Show Details)
Krinkle moved this task from Under discussion to P4: Tune on the TechCom-RFC board.

Is there anything I can help with to push this RFC forward?

Just to make this topic easier to process - what we want to achieve is easily accessible "Feature Toggles" that can be used anywhere in the MediaWiki land. For more information please read https://martinfowler.com/articles/feature-toggles.html

Currently we handle feature toggles with config flags. This was pretty easy to achieve when we were dealing with one/two flags only. Now, newly built features depend on different features, that depend on set of various configs/user preferences. Instead of dealing with the complexity in each extension one by one, the goal is to have one extensible system that allow us to develop faster without spending too much time on feature toggling logic.

"Requirements" from Task description:
  • disable/enable multiple features at once
  • do incremental rollouts where feature depend upon each other and only one feature is available for public
  • promote some parts of functionality into default experience but still keeping rest disabled
  • A/B test some features for a short time

Nice :) Can you describe a bit how these are or would be fulfilled by the Feature Manager?

From Task description:

Existing solution: […] In the system we only call $featuresManager->isAvailableForCurrentUser( 'AdvancedMainMenu' ) and everything happens under the hood.

Author recommendation: […] we learned we could not depend upon RequestContext in ServiceWirings file, and the system has to pass current IContextSource when performing feature checks.

Passing IContextSource is only valid in user-facing code though. Is the feature manager intended for use outside that? E.g. in code that might be directly or indirectly called by other service classes, search-related code, Echo-related code, ResourceLoader code, rest.php, job queues, maintenance cron etc.

For these, it might make sense to offer a "safe" method that does not require a User parameter, and will not e.g. send any A/B cookies – some kind of deterministic default.

Is there anything I can help with to push this RFC forward?

See https://www.mediawiki.org/wiki/Requests_for_comment.

At this stage it woud be good to document which teams and/or communities you've heard from and which perspectives you're still waiting for feedback from. E.g. should we involve Security or Privacy? Do you want to hear from PMs about possible needs we might need in the future to make sure they won't require a full rewrite to support? Also part of Phase 4 is to review your preferred solution (3) against the Architecture Principles and identify any aspects that are uncertain to you or might need further discussion.

Krinkle changed the task status from Open to Stalled.Jul 29 2020, 8:37 PM

Cache variation
If any of these A/B tests result in a change in output for anonymous users, we need to split the cache along this dimension. In other words, we wouldn't want that all users get feature A because that was the one cached.

I've added a note about this to the requirements in the task description, please feel free to revert if this seems wrong.

Combinations of mode for separate features?
This seems like a fundamental challenge to generalizing the "mode" concept beyond MobileFrontend. It's possible to limit each extension to only one beta feature at a time—although we can expect to chafe at that as the new configurability makes A/B testing more prevalent. But we definitely can't have a single "beta" mode which toggles all new features across every extension because then tests in more than one extension would become coupled and what should have been e.g. 4 test populations collapses into 2 like: (A1, A2) and (B1, B2), the heterogenous crosses are missing.

Perhaps we say that a user can be in only one experimental mode at a time, but that makes all tests much lower-traffic. (There are some pro's to this approach as well, it simplifies test conditions, and turns the cache split count into an addition rather than multiplication formula.)

So let's say that the mechanism needs to support independent modes for each extension or feature. What does it look like now? I would leave this open to further exploration.

As a new extension?
I would propose that we consider packaging this new component as its own extension. This solution has all of the same benefits as implementing in core, except breaks "no external dependencies". It also eliminates the con "longer review cycle". It adds a con "integrations must test for availability".

Including as an extension eliminates complexity for those who don't need A/B testing or an isolated dev environment. It gives us some freedom when implementing. It makes it easy to completely disable if we decide not to use in the future.

What I imagine is a request handling sequence like,

  • Extension hooks into early configuration, by BeforeInitialize or a new, dedicated hook.
  • Read mode sources (cookie, user preferences, etc.)
  • Process the cross product of modes and mode-sensitive configuration.
  • Updates configuration as necessary.
  • Adds a Vary: header if we should split the output or page cache.

If others agree, the proposal to package as a new extension could be added to the RFC body as proposal 5.

@awight RFCs have shifted to the Technical Decision Making Process (TDMP), so if you want to follow up on this, I guess the only way would be to introduce it there.
@Jdrewniak might want to be involved too, I see him subscribed here and I don't know how relevant this is to his work on the Chrome Extension and Desktop Refresh in general.

I've started to document how feature management works in the wild. I'm unlikely to work on this document any more until 2022, but feel free to edit it and improve it if you feel inclined! https://www.mediawiki.org/wiki/User:Jdlrobson/Feature_management_in_MediaWiki_(for_developers)

@phuedx is there a newer ticket we could merge this into?