Extensible User Picker

Context

In the context of ActivityPub, we want to be able to use the user picker to suggest users from the fediverse.
In addition, we want to be able to use the user picker to suggest users, groups, and users from the fediverse when using the mention macro.

Hence, it is interesting to allow the user picker to propose results from different sources of groups or users, and to allow these sources to be contributed to by extension.

Overview

I propose to introduce an UIXP used to call specific documents dedicated to the search of actors of a given kind (e.g., users, groups…).

Current Architecture

The user picker (suggestUsersAndGroups.js) calls uorgsuggest.vm to get the list of suggestions.

Parameters

  • exactMatch (boolean): if true, return only exact matches
  • input (string): the string used for the search
  • wiki ('global' or undeclared): if ‘global’, search for global users too
  • media ('json' or undeclared ): if ‘json’ returns the result in json, otherwise returns the result in xml.
  • uorg: ('user' or undeclared): if ‘user’ search for the users, otherwise search for the groups

Response

The result of the query is a data structure (json or xml). The number of results is limited to 10.

xml

The root is a result tag containing multiple rs tags. The rs tag has multiple attributes:

  • id: the url of the user or group
  • icon: the url of the icon of the user of group
  • info: the plain user name or the group name

The body of rs tag is the compact serialized form of the user or group

json

The result is an array of objects.
Each object has the following fields:

  • value: the compact serialized form of the user or group
  • label: the plain user name or the group name
  • icon: the url of the icon of the user or group
  • url: the url of the user or group

Proposed Architecture Changes

The parameters of uorgsuggest.vm stay the same but uorg is used differently.

  • if empty: returns the groups (for legacy reasons)
  • if equals to ‘*’: returns the results of all the UIX.
  • otherwise, the string is split and only the UIX with a parameter type contained in the split list are used to form the results.

For instance,

  • users,groups returns the suggestions of users and groups
  • groups returns only the groups suggestions
  • the empty string returns the groups suggestions too
  • users,activitypub returns the suggestions for the wiki users and activitypub users.

The result stays the same but a new key, named type is introduced. The type indicates from which UIX the results comes from.

The result of all the selected UIXs are merged, sorted by label, limited to the 10 first results, and possibly converted to XML, before being returned.

UIXP:

The UIXPs return only json results.

The conversion into XML is done only if needed after the results are merged and sorted.

  • UIXP id: org.xwiki.platform.web.userOrGroupPickerSource
  • Attributes:
    • type = unique type

Parameters:

  • exactMatch
  • input
  • wiki

Use

While the uorg parameter is empty, groups our users, the use of the user picker stays the same.
When uorg is * or a specific set of types, the type of the returned value must be used to exploit the returned values in a relevant way.

New UIX

The results for the users and groups are currently defined inside uorgsuggest.vm.
They should probably be moved to two dedicated UIX.

WDYT?

What happens if you request users in all UIX (so with *) but users already return 10 results? I’m wondering if we shouldn’t have another strategy such as splitting the number of results by number of UIX with at least 1 result per UIX.

So for example, I’m asking 10 results for users,activitypub, we return 5 users for users and 5 for activitypub. If we’re asking for 2 results for users,activitypub,group we’re asking for 1 results for each of them. etc. wdyt?

What I propose is to sort the aggregated results alphabetically.
In this case, unless all the match results from a given source are starting with the letter A, chances are high that the results of different sources will be included in the 10 first results.

What I observed by doing some tests on a wiki with many users, the result quickly converge after a few strokes even with a large number of users.
It is probably not very useful to have a complex solution for this as it would not bring much user experience improvement IMO.

What we could do, is to keep a cache of the last mentions of a user, and to propose them at the top of the list if they match.

Not sure I’d agree with that one since it might imply to mix the results coming from different sources. Moreover on which field would you rely on to perform the sort? For example if all results coming from ActivityPub are starting with @ they will probably always appear first.

I would use whatever is returned on the label field, which is expected to contain the human readable representation of the user. The technical identification of the user is stored in the value field.

In the case of AP, this should be the name field of the webfinger query, or the preferredUsername if it does not exist.

Well anyway it could be a later improvment. +1 in general for the UIX with the proposed architecture.

Disclaimer: replying quickly, haven’t processed everything in this thread yet so I could be off.

I’m not sure we need a UIXP. Wouldn’t a parameter in the userpicker macro be enough? That parameter would specify the template to use (similar to how the LT macro work: you can pass the page that returns the JSON).

A UIXP is related to skins normally so I’m a bit wary of using that for something that doesn’t seem related to a skin.

And also a UIXP is the last resort (it’s not typed and thus it’s not a great discoverable API). Thus when it’s possible to pass something to the call, it’s better to do that.

The main benefit of the UIXP is the opportunity to automatically have new sources contributing to the auto-completion without having to update the user picker definition (when uorg=*).

If this is not something we want, then an UIXP might not be the best choice.

In this case, I have two alternative solutions in mind:

  1. introducing a new parameter that takes a list of templates to be used for the auto-completion search
  2. replacing the current template based system by a java script service that would look for “user of group search components”. In this case, the values passed by uorg could be used as the hint for the components lookup.

The second solution is probably better in term of maintainability and testability (and uorg=* would still be possible) but might be limiting if we want to let contributors add their own auto-completions sources.

WDYT?

My point is that UIXP are not meant for this. Look at the search suggest sources for example. They don’t use UIXP and yet they support new sources. UIXP is not a magical solution for everything. It’s a way for extensions to enhance skins. Your use case doesn’t seem to be a skin use case to me (I could be wrong).

There are plenty of other ways to make something pluggable: XClass, java components, etc.

That’s actually the benefit of a component in general. UIXP is just one kind of component than comes with a wiki bridge but maybe a more dedicated one would make more sense if those are not pieces of wiki content to render (which is UIX target).

I can propose four alternatives to modularize the sources of the user picker auto-completion.

UIXP
From the discussions above we can probably say that this is not the best solution.

Live Table like
In this case, when calling the user picker macro, a set of pages to use as JSON providers is passed in parameter (close to the use of LT’s resultPage parameter).

Pro:

  • new sources can be defined at runtime

Cons:

  • Having a ‘*’ parameter is not possible
  • uorg should probably be deprecated and a new parameter introduced for the list of the pages

Search Suggest like
In this case a UserPickerSearchSource XClass is introduced and XObjects defined for each sources.
And administration page can be proposed to configure the search sources.

Pro:

  • more structured than LT
  • new sources can be defined at runtime

Java Components based
In this case, a UserPickerSearchSource role is introduced, and components defined for each source.
A script service is used to lookup the requested components and aggregates their results.

Pros:

  • probably better in regard to performance and testability

Cons:

  • more complex to update sources at runtime

As said several times relying on components does not mean it has to be implemented in Java, nothing prevent you from providing a bridge (like we do for UIX and many other wiki components) if you really think that it make sense to have user sources implemented in wiki pages (sounds a bit strange to me but I did not looked at all the details).

Thanks, I guess you refer to Allow a new component to be instantiated through XObjects.

In this case the Java Components based has one less cons.
This bridge can even be provided in a second time. While this is interesting in term of flexibility, I am not sure it is a feature that will be used often as providing a new search mechanism will probably require some Java development anyway.