Page MenuHomePhabricator

Community configuration: Add support for dynamically-hidden providers
Closed, ResolvedPublic3 Estimated Story Points

Description

As part of T361123: Make it possible to declare a Community configuration provider that does not display on the dashboard, we allowed client extensions to declare certain providers as hidden. This makes it possible to eg. migrate structured mentor list to CommunityConfiguration (T367575), as such list includes data and should not be available in Special:CommunityConfiguration at all.

However, there are cases when an extension would only want to display a provider assuming certain conditions are met. For example, GrowthExperiments might want to only display Help panel configuration when Help panel is enabled (same for Homepage). Those features have a dedicated provider, and for them, hiding individual fields (possible since T367331) is not sufficient.

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript

Sharing the alternatives brainstormed with @Michael in sync meeting:

  • Option 1: similar to the approach followed in T367330, introduce a hook that allows CC consumers to override the list of supported providers returned by ConfigurationProviderFactory::getSupportedKeys
  • Option 2: similar to or enhancing the provider option skipDashboardListing: true, use a server configuration name to evaluate, eg: skipDashboardListing: "GEMentorshipEnabled"

So far, even if GrowthExperiments use cases would probably only evaluate a server setting in the hook, Option 1 seems more flexible to allow other kinds of checks.

KStoller-WMF set the point value for this task to 3.Jun 25 2024, 4:11 PM

Change #1049920 had a related patch set uploaded (by Sergio Gimeno; author: Sergio Gimeno):

[mediawiki/extensions/CommunityConfiguration@master] Add hook for dynamically hiding form fields

https://gerrit.wikimedia.org/r/1049920

Adding Option 3 based on 1:1 conversation with @Urbanecm_WMF:

What I am wondering about is whether it would make sense to make skipDashboardListing support callbacks, rather than just booleans. Then, we could evaluate that callback and do whatever the callback says. Granted, it would be hard (if not impossible) to use proper DI in that code, but maybe that's not an issue when limited to a single static callback?

  • Option 3: make skipDashboardListing accept a callback and hide the provider from the dashboard based on the result of calling it

It is also been brought in review comments in the PoC patch for Option 1 about the difference from hiding (from the Dashboard) vs un-registering. The task title language is hide and the description mentions would only want to display a provider assuming certain conditions. The HelpPanel example is an example of a broadly available feature but not enabled in all wikis. The other use case I tried to pursue in the PoC is for Community updates, which is a feature not yet available and not yet enabled. Since providers are automatically supported once declared in extension.json CommunityConfiguration will use the list of providers not just in the dashboard capabilities to list providers but also in other places, these are the ones I'm accounting for:

  1. NavigationHooks::onSkinTemplateNavigation__Universal: to add/remove links in both form and config page
  2. SpecialCommunityConfiguration::execute (isProviderSupported): to decide if the parameter in the URL matches a supported provider and it should render an editor form
  3. MediaWikiConfigReader::computeVariableToProviderMap: for config reads
  4. DashboardEditorCapability::getProviders: to list the provider in the dashboard or not
  5. ValidationHooks::onJsonValidateSave: to trigger validation on the json page edits

The problem I see with Option 3 is the current scope of skipDashboardListing is narrowed to the dashboard capability (4) and the SpecialCommunityConfiguration case (2). It seems we've released the skip dashboard feature but we have no consumer yet per codesearch results. The original use case was NewcomerTopicOres.json, (T361123), but this task also mentions the Mentor list. Current status quo is if we use skipDashboardListing: true the provider is indeed not listed (4) and shown as not found in Special:CommunityConfiguration/<provider_name> (2) but we add the "View form" link in the config page (1) redirecting then to the editor page users will get Provider GrowthSuggestedEdits is not found. We also allow reads from this provider (3) and we trigger validation if a config page is declared (5).

I am not sure which use case will make use of skipDashboardListing but I'd suggest that we extend its scope at least to the "View form" link (1), which imo does not help. Also maybe the copy used if there's a URL match should change from "Provider not found" to "Provider skipped" (2). About config reads and triggering the validation hook I am not sure yet. It could be benefitial or problematic depending on the situation.

For the HelpPanel and CommunityUpdates use cases it could be usefu,l to "prepare" a config before enabling the feature, to have some validation and being able to read it. Otoh if there's an existing config blob that is invalid it could be problematic as the edits to that page would require to validate against the schema, users could be tempted to actually fix the validity errors and that result in unexpected system behaviors.

Based on these observations two alternatives I can see are:

  • A) Option 1 with scope for all (1,2,3,4,5) and maybe (separate task) extend scope of skipDashboardListing to (1)
  • B) Option 3 with extended scope of skipDashboardListing for all (1,2,3,4,5)

What do you think? cc @Urbanecm_WMF @Michael

I forgot about the Mentor list use case where the form is completely handled on the consumer side. A maybe more appropriate approach to account "for all" is:

  • C) Option 3 but using a different option name, eg: skipRegistering which uses the callback approach and keep skipDashboardListing with its current scope

In all situations I think we should do something about the "View form" link (1), hide it, or allow to set the link URL to the consumer, so the mentor list json page can link to the manage mentors special.

Thanks for the write-up, @Sgs. Based on what you said, I think we can separate possible actions ("hidings") into two main categories:

(A) hiding a provider from the UI,
(B) fully disabling the provider.

Both of those actions are potentially useful. Option (A) is helpful when we do not want to benefit from the UI system of CommunityConfiguration. This might be when CommunityConfiguration is used merely as a data backend. In GrowthExperiments, this is useful for structured mentor list (T367575). It is also potentially useful for ORES topics, but as noted in T367576, it is possible we would move them to server config rather than moving to CommunityConfiguration. In both of those cases, we want to be able to actively access the configuration (and possibly even modify it), but we do not want to expose it to the admin (because we have our own interfaces for changing it, or because of other reasons).

Option (B) would be useful when a given provider should not be even accessed, which is the case for unavailable features (such as CommunityUpdates). In those cases, it does not make any sense to access the configuration, as the feature is not present on the wiki. If something does try to access the relevant provider (where as for writing or for reading), it is a sign of a bug.

As of now, we have option (A) implemented with a limited scope (it only works in the dashboard, and not anywhere else). As @Sgs says, this should definitely be fixed. As for option (B), we do not have that available at all. Since option (B) is what we want for CommunityUpdates, I think we should:

  • implement option (B) in this task,
  • fill another task for improving option (A).

The good question of course is how do we want to implement option (B) in CommunityConfiguration. We have two competing implementation plans: introduce a new hook for option (B), or introduce a new provider option (skipRegistering in @Sgs's post above). Both of those are useful in certain cases: hooks are by definition more powerful, as unlimited logic can be used in them. Callbacks are mainly intended for simplish logic. Only the hook would be usable if we ever wanted to dynamically register a provider, as opposed to unregistering it.

I think our use-case here is similar to eg. special page registrations. Those can happen either in extension.json (if they're applicable to all wikis), or via a hook (if they only apply in a certain case). I think we should use the same approach, as it is already likely to be known to other MediaWiki developers. I also believe it is going to be easier to write code that will register the provider for cases when you need it, as opposed to registering the provider unconditionally, and then coding for the negated condition.

For CommunityUpdates, this might not be an issue, as CommunityUpdates will be controlled by a single feature flag. However, if the condition for making a feature available is a composite one ("has extension X loaded AND feature flag is true"), negating it might get potentially tricky, and it is very easy to end up with a error in the code.

Taking all of the above into account, I recommend we introduce a hook to conditionally register providers, akin to onSpecialPage_initList from Core. That hook can then modify the list of providers, which is currently generated in ServiceWiring (see how CommunityConfiguration.ProviderFactory constructs its ServiceOptions instance), conditionally adding their own stuff into it. That way, CommunityUpdates could define its provider in this way, instead of using extension.json attributes.


@Sgs @Michael What do you think about those recommendations? Do you see any risks or downsides I failed to notice/mention? Do you see any alternative approaches we should think about and considr?

@Sgs @Michael What do you think about those recommendations? Do you see any risks or downsides I failed to notice/mention? Do you see any alternative approaches we should think about and considr?

That sounds ok for me, I re-purposed 1049920 in the Provider_initList direction.

I filled T368728: CommunityConfiguration: skipDashboardListing should fully hide the provider from UI for the "improve option A" part.

Thanks!

Providing a hook to actively register providers sounds sensible. I would also add another idea that I thought of upon hearing that option:

At least for Community Updates, it might be good enough in the beginning to merely define the provider in configuration instead of in code. We have the CommunityConfigurationProviders config option that we use in tests. Would that not also work in production config? That might not be the ideal thing to do long-term, but would be the quickest way to unblock community updates on beta, because it does not need any coding, just a beta-config change.

That being said, that config option in extension.json does not have a description, so I'm not fully sure about its purpose and contract.

Providing a hook to actively register providers sounds sensible.:

Could you elaborate on the sensitivity arguments. I find removals sensitive in the sense one extension can unregister providers from another. For additions I don't think there's any major issue.

At least for Community Updates, it might be good enough in the beginning to merely define the provider in configuration instead of in code. We have the CommunityConfigurationProviders config option that we use in tests. Would that not also work in production config? That might not be the ideal thing to do long-term, but would be the quickest way to unblock community updates on beta, because it does not need any coding, just a beta-config change.

That would indeed be enough for Community updates in Beta but I feel it could quickly become a maintenance burden for scaling Community updates and others (eg T367656). I guess we're looking for the mid/long-term solution, any thoughts around it?

That being said, that config option in extension.json does not have a description, so I'm not fully sure about its purpose and contract.

Indeed, I tried to amend that in 1051091. We probably need a docs section for this extension config options.

For now, in order to account for Community updates scaling and T367656 I think the hook approach gives us the flexibility we need due to the openness of CC2.0 adoption processes that we can find: adopting from scratch, migrating from some sort of logic (CC1), migrate from a server config, migrate from a data option, etc. Unless there are strong concerns with the _initList approach I suggest we move it forward to unblock other tasks and request feedback to adopters (other than Growth) about which are their needs around feature toggling and scaling.

Providing a hook to actively register providers sounds sensible.:

Could you elaborate on the sensitivity arguments. I find removals sensitive in the sense one extension can unregister providers from another. For additions I don't think there's any major issue.

"sensible" not "sensitive".

At least for Community Updates, it might be good enough in the beginning to merely define the provider in configuration instead of in code. We have the CommunityConfigurationProviders config option that we use in tests. Would that not also work in production config? That might not be the ideal thing to do long-term, but would be the quickest way to unblock community updates on beta, because it does not need any coding, just a beta-config change.

That would indeed be enough for Community updates in Beta but I feel it could quickly become a maintenance burden for scaling Community updates and others (eg T367656). I guess we're looking for the mid/long-term solution, any thoughts around it?

My main thought for suggesting this was that it might be useful in case there is any commitment to meet for having something accessible on beta today-ish. I agree that for scaling and mid-term, this is not the way to go. The proposed approach to add the config for the provider dynamically sounds good.

That being said, that config option in extension.json does not have a description, so I'm not fully sure about its purpose and contract.

Indeed, I tried to amend that in 1051091. We probably need a docs section for this extension config options.

Thanks, I'll see if I can leave some feedback there.

For now, in order to account for Community updates scaling and T367656 I think the hook approach gives us the flexibility we need due to the openness of CC2.0 adoption processes that we can find: adopting from scratch, migrating from some sort of logic (CC1), migrate from a server config, migrate from a data option, etc. Unless there are strong concerns with the _initList approach I suggest we move it forward to unblock other tasks and request feedback to adopters (other than Growth) about which are their needs around feature toggling and scaling.

I agree.

Change #1049920 merged by jenkins-bot:

[mediawiki/extensions/CommunityConfiguration@master] Add hook for dynamically registering providers

https://gerrit.wikimedia.org/r/1049920

Given this is been in production for two months now, moving it to Test in production.