Sort the search results on the member search by a custom field

Can the search results on the member search page be sorted by an ExtendedAttributes or ProfileFields field?

Parents
  • Unfortunately there's not a direct option to list by those fields, see User Script API - List. There is limited logic to perform on-page sorting in Velocity, some capacity in Javascript. However on-page sorting would be limited by paging that is based on a different sort order. You could also expose your own widget extension that could collect all the users from the User In Process API and re-sort them by your desired field. Both the aggregation and the re-sort would be potentially costly operations computationally.

  • There is limited logic to perform on-page sorting in Velocity,

    What is this logic?

    You could also expose your own widget extension that could collect all the users from the User In Process API and re-sort them by your desired field.

    How would I go about this?

  • 1.  Velocity extensions return C# objects that can be manipulated with C# methods, but the availablity of certain methods at render time may not be guaranteed. And again - the sorting would be limited based on a paged set of results based on another sort.

    2. I provided some links that should be able to get you started - you'll need to create a project/solution in Visual Studio and build custom code to drop into your site's Web/bin folder. The links go to the Developer Training area and the full API Reference respectively, which should be most of the general information you need. Do you have specific questions here?

  • I know how to write a widget extension, will I be able to access the extendedattributes of a user object in C#?  What would I pass into the widget extension? Is it the search results list returned from this statement? 

    #set ($searchResults = $core_v2_searchResult.List($searchListOptions))

    #foreach ($result in $searchResults)
    	#set ($user = false)
    	#foreach ($resultUser in $result.Users)
    		#set ($user = $resultUser)
    	#end

  • Using the in-process API in the widget extension, you can access all properties of the user. So your pseudocode would be something like:

    1. Retrieve all users page by page (again: costly based on your number of users)
    2. Sort them by comparing on your desired field
    3. Return them to Velocity via your widget extension, either as a full list or paged.

    Again to reiterate, retrieving all users in this way, especially if you then page results of your new sorted list, is going to be quite expensive. At that point it might be better to set up a custom data store (i.e., your own database table) that keeps your custom fields referenced by userid, and then use the user list API and provide a list of ids via the ContentIds option with SortBy=ContentIdsOrder. Then you can return that pre-paged, pre-sorted list via your widget extension.

  • This is my first attempt at a widget extension using the in-process API, but it doesn't seem to return any results after the sorting:

    The widget extension C# code:

    using Telligent.Evolution.Extensibility.UI.Version1;
    using Telligent.Evolution.Extensibility.Api.Entities.Version1;
    using System;
    using System.Text;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace Utils
    {
        public class Utilities
        {
            public string Base64Encode(string base64Decoded)
            {
                byte[] data = ASCIIEncoding.ASCII.GetBytes(base64Decoded);
                string base64Encoded = Convert.ToBase64String(data);
                return base64Encoded;
            }
    
            public string ConvertTimestamp(int ts)
            {
                DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(ts).ToLocalTime();
                return dt.ToString("MMM dd, yyyy");
            }
    
            public List<SearchResult> SortSearchResults(List<SearchResult> ls)
            {
                List<SearchResult> SortedList = ls.OrderBy(o => o.Users[0].ExtendedAttributes["SortOrder"]).ToList();
                return SortedList;
            }
    
    
        }
    
        public class UtilsWidgetExtension : IScriptedContentFragmentExtension
        {
            #region IScriptedContentFragmentExtension Members
    
            public string ExtensionName
            {
                get { return "Ibby_v1_Utils"; }
            }
    
            public object Extension
            {
                get { return new Utilities(); }
            }
    
            #endregion
    
            #region IPlugin Members
    
            public string Name
            {
                get { return "Utilities"; }
            }
    
            public string Description
            {
                get { return "Utilities"; }
            }
    
            public void Initialize()
            {
            }
    
            #endregion
        }
    }
    

    The Velocity code:

    #set ($searchResultsOriginal = $core_v2_searchResult.List($searchListOptions))
    #set($searchResults = $Ibby_v1_Utils.SortSearchResults($searchResultsOriginal))
    
    ## Render Search Results
    
    #set ($hasMore = false)
    #set ($currentPagedQuantity = ($searchResults.PageIndex + 1) * $searchResults.PageSize)
    #if ($searchResults.TotalCount > $currentPagedQuantity)
    	#set ($hasMore = true)
    #end

    What could the problem be?

  • Have you tried debugging and stepping through to confirm that the method is called and the fields you are using to sort compare are there?

  • I'm not sure I know how to reproduce the user data in my Telligent installation on the C# side for use in debugging and stepping through. Any thoughts?

  • when you copy your .dll into Web/bin, also include the .pdb of the same name. Then you can attach to w3wp process in Visual Studio and set a breakpoint in the sort method.

  • The method is not getting called. I can't figure out why.

  • It appears to be working now. I am now passing the search results list as an array instead of a list. Thanks.

Reply Children
  • Again to reiterate, retrieving all users in this way, especially if you then page results of your new sorted list, is going to be quite expensive. At that point it might be better to set up a custom data store (i.e., your own database table) that keeps your custom fields referenced by userid, and then use the user list API and provide a list of ids via the ContentIds option with SortBy=ContentIdsOrder. Then you can return that pre-paged, pre-sorted list via your widget extension.

    Hi Stephen,

    Could you please explain this procedure a bit more. What would I pass into my widget extension method, and what would I pass out? What would be the pseudocode for the procedure?

  • You need a custom table to store whatever you want to sort y and the contentId and any associated code to make your call.  That list of Ids can be passed to the User list  and be returned in the same order.

    https://community.telligent.com/community/11/w/api-documentation/63544/userslistoptions-in-process-api-supplementary-type

  • I am returning the sorted Users list from my widget extension, but the users are no longer being filtered on the Member Search page. How can I get the filtering working again?

  • There is not enough information here to answer this because we don't know what you did or how anything was implemented.  If the expectation is that you added your own extension to replace a call on that page and it would just work, then that won't be the case.  

  • Hi,

    Here's the C# code:

       public class MyUsers
        {
    
            public string GetContentIds()
            {
                SqlDataAccess sd = new SqlDataAccess();
                using (SqlCommand cmd = sd.GetCommand("ibby_Get_Sorted_User_Content_Ids"))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    DataTable dt = sd.Execute(cmd);
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < dt.Rows.Count; i++)
                    {
                        sb.Append(dt.Rows[i]["ContentId"].ToString());
                        if (i != dt.Rows.Count - 1)
                            sb.Append(",");
                    }
                    return sb.ToString();
                }
            }
    
            public PagedList<Telligent.Evolution.Extensibility.Api.Entities.Version1.User> GetUsers()
            {
                UsersListOptions options = new UsersListOptions();
                options.ContentIds = GetContentIds();
                options.SortBy = "ContentIdsOrder";
                return Apis.Get<Users>().List(options);
            }
    
        }
    

    And search.vm

    #set($filters = $core_v2_page.GetFormValue('filters'))
    
    ## perform search
    #set($count = 0)
    
    #set ($searchListOptions = "%{ Query = $searchQuery, Sort = 'titlesort', PageIndex = $pageIndex, PageSize = $pageSize }")
    $searchListOptions.Add("Filters", "type::user${filters}")
    ##set ($searchResults = $core_v2_searchResult.List($searchListOptions))
    
    #set ($searchResults = $!$ibby_v1_UserData.GetUsers())   
    ## Render Search Results
    
    #set ($hasMore = false)
    #set ($currentPagedQuantity = ($searchResults.PageIndex + 1) * $searchResults.PageSize)
    #if ($searchResults.TotalCount > $currentPagedQuantity)
    	#set ($hasMore = true)
    #end
    
    #foreach ($user in $searchResults)
    #beforeall
    
    	<div class="content-list thumbnail ui-masonry margin-top" id="$core_v2_encoding.HtmlAttributeEncode($core_v2_widget.UniqueId('thumbnails'))" data-columnclass="thumbnail-column">
    #each
    	##set ($user = false)
    	##foreach ($resultUser in $result.Users)
    	##	#set ($user = $resultUser)
    	###end

  • These are 2 completely different methods for getting users so filters are not going to work.  You use the users API, the members page is based on search.  These methods are not interchangeable.

  • Realistically, you could just use Apis.Get<ISearchIndexing>().Events, handle the BeforeBulkIndex event for users, stuff a custom field in the document for the value you want to sort by and then modify any call the Apis.Get<ISearchResults> to include your custom sort in the query

    community.telligent.com/.../searchindexing-in-process-api-service

  • How often does the BeforeBulkIndex event get fired in a user's lifecycle? Is it only once? My custom sort field can change quite often, and it might not have a value before the event gets fired. Will that be a problem?

  • That event gets fired before altered users get changed in the index, which varies by how often the job runs and how many changes need to be processed.  You still will have to store this value somehow and read it when the event is fired

  • I've attempted to write some code for this. Would this work as a widget extension?

    public class MyUsers
        {
            public Telligent.Evolution.Extensibility.Api.Entities.Version1.SearchResults GetUsers(int pageIndex, int pageSize, string filters, string query)
            {
                ISearchResults search = Apis.Get<ISearchResults>();
                var searchOptions = new SearchResultsListOptions
                {
                    PageIndex = pageIndex,
                    PageSize = pageSize,
                    Query = query,
                    Filters = filters
                };
                searchOptions.Sort = "UserSortOrder";
                var results = search.List(searchOptions);
                return results;           //Apis.Get<Users>().List(options);
    
            }
    
            public void BeforeSearchBulkIndexingEventHandler(BeforeBulkIndexingEventArgs e)
            {
                SearchIndexDocument d = new SearchIndexDocument();
                d.AddField("UserSortOrder", GetSortOrder(d.ContentId));
                e.Documents.Append(d);
            }
    
            private string GetSortOrder(Guid contentId)
            {
                SqlDataAccess sd = new SqlDataAccess();
                using (SqlCommand cmd = sd.GetCommand("ibby_UserSortOrder_Get"))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.AddWithValue("@ContentId", contentId);
                    DataTable dt = sd.Execute(cmd);
                    if (dt.Rows.Count > 0)
                        return dt.Rows[0]["UserSortOrder"].ToString();
                    else
                        return string.Empty;
                }
            }
        }