Customizing Search Queries

cancel
Showing results for 
Search instead for 
Did you mean: 

Customizing Search Queries

ddraper
Intermediate II
2 4 5,789

Update 14/11/2016

From version 1.0.96 of Aikau this post is out-of-date in that the issue was raised as a feature enhancement and a solution was implemented to support adding additional query parameters through configuration of the additionalQueryParameters on the AlfSearchList widget.

Introduction

Now that we have this great new Community platform it should be easier for me to stay on top of Aikau and Share customization related questions. In particular I'm going to try and set aside some time each day to work through any interesting use cases that are raised in the ECM space.

This is the first question that I'm going to provide an example for. The requirement is to be able to customize the search results page so that only documents are shown. There are actually a number of different ways in which this problem can be solved using Aikau and I wanted to work through a few of them.

The various solutions fall into two different categories:

  1. Update the query before it is sent to the Repository
  2. Filter out any unwanted items from the response

Use renderFilter?

The second option could be achieved by creating an extension module to change the configuration of the search result widgets to only display when the "type" attribute of the result is "document". This would be done using the renderFilter configuration and would mostly work with a couple of caveats:

  1. If items were filtered out then the displayed search result count would not match the number of results displayed
  2. If only non-documents were found and all of them were filtered out then the user would not see the "No results found" message.
  3. If only non-documents made up the first page of results, then it would not be possible to trigger the infinite scrolling events to load the next page of data.
  4. It would be necessary to apply the configuration to widgets in each view (the search page has two views out-of-the-box).

So it looks like the renderFilter configuration approach is not the best solution in this case, but definitely one to be aware of when solving other problems. However, the configuration would look something like this:

...
"renderFilter": [
  {
    "property": "type",
    "values": ["document"]
  }
]
...‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Some Background on Search

So in order to avoid the problems that would be introduced with the renderFilter approach we want to customize the search query before it is made. At this point it would probably be helpful to explain a little bit about how the search page (and Aikau in general) works.

The AlfSearchList widget is responsible for managing search state. It tracks search requests and manages the scope and facet filters that the user wishes to apply. It also manages what page of data is being displayed as well as the required sorting information and views that can display the data.

However it does not actually make the REST API call to perform the search, nor is it responsible for rendering the results. The REST API call is actually made by the SearchService which subscribes to a search request topic that the AlfSearchList publishes.

Therefore we have two choices - we can customize the AlfSearchList so that the request payload is updated before it is published, or we can customize the SearchService so that the payload is updated when it is received.

Configure AlfSearchList?

The ideal solution here would be if we were able to re-configure the AlfSearchList to force it to include additional data in the search payload. Unfortunately at the time of writing (when the latest Aikau release is 1.0.85) this capability does not exist. If you encounter this situation when trying to customize Share then ideally we would like you to raise an issue on the Aikau GitHub project, like this.....  or better yet - provide an update via a pull request

Customize AlfSearchList?

Since there isn't a configuration option available to us at present, then next option is to consider extending AlfSearchList with out own custom widget. The AlfList (which is an ancestor of AlfSearchList) provides the updateLoadDataPayload function for exactly this purpose - but sadly (due to the history of the development of AlfSearchList) this function is not called. If you were to encounter a problem like this then it would also be a good candidate for an issue!

Customize SearchService

So this leaves (at the time of writing at least) the best option as being to extend the SearchService. You can define an extending module to do this like this:

define(["dojo/_base/declare",
        "alfresco/services/SearchService"],
       function(declare,SearchService) {

  return declare([SearchService], {
    onSearchRequest: function(payload) {
      payload.term = payload.term + ""
      payload.datatype = "cm:content";
      this.inherited(arguments); // This calls the superclass function
    }
  });
});‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

This shows the onSearchRequest function being extended to update the payload before the superclass function is called. In the example shown above we are showing a couple of options:

  1. Appending to the search term
  2. Adding a query parameter to set the datatype to be "cm:content"

(Note: Any payload attribute unknown to the SearchService is treated as a query parameter!)

Creating An Extension Module

The thing we need to do is to create an extension module that can be deployed into Share to apply our changes. This module needs to do 3 things:

  1. Define an AMD package for our custom service
  2. Customize the search model to swap out the original service for our custom service
  3. Provide the custom service

Creating an extension module for a "full" Aikau page is incredibly simple using the "Developer View" and is described in detail in this previous blog post.

We will need to update the extension module to add in a new AMD package and this is described in this previous blog post.

In order to customize the search page model we will need to edit the "faceted-search.get.js" file that is generated for us to look like this:

var services = model.jsonModel.services;
for (var i=0; i<services.length; i++)
{
   if (services[i] === "alfresco/services/SearchService")
   {
      services[i] = "blogs/CustomSearchService";
   }
   else if (services[i].name === "alfresco/services/SearchService")
   {
      services[i].name = "blogs/CustomSearchService";
   }
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

We iterate over the services (which can be defined either as a simple string or as an object) and when we find the default search service we swap it out for our custom service.

The End Result

Once you've re-packaged your extension module as a JAR file you can drop it into the "share/WEB-INF/lib" folder (or include it in an AMP file, or port the code into an SDK project to build an AMP) and restart Share. All subsequent searches on the search page will only return items that are "cm:content".

The problem is that a lot more things are "cm:content" than you might realise. The question author only wanted to show documents, but "cm:content" does not mean "document". If you have a special type defined that you're using then this would work, actually though what you're probably going to want to do is to append some extra stuff the search term. This sort of thing in fact...

AND -TYPE:"cm:thumbnail" AND -TYPE:"cm:failedThumbnail" AND -TYPE:"cm:rating" AND -TYPE:"fm:post" AND -ASPECT:"sys:hidden" AND -cm:creator:system'‍‍

Note: In fact this query still doesn't completely filter everything out, but you get the general idea of what is required

Download and try it out

I've made the code available on this GitHub repository for people to review in more detail and try out. There is even a ready made JAR file that you can download and test with.

If you've found this post useful or interesting then it would be great if you could like or share it from the top of the page!

4 Comments
afaust
Master

Personally I find the approach to extend / modify the search service not really acceptable. Since the search service is a singleton and can accept search requests from different widgets, it needs to be generic and can't include such customizations as "appending a query fragment". There are already too many such ill-conceived assumptions / behaviours in out-of-the-box Alfresco (i.e. search.lib.js in Alfresco 5.1 filters out items created by System-user for some reason, which might suppress bootstrapped contents).

In a recent project I did for a customer in Switzerland I extended the AlfSearchList instead and added a configuration option "baseQuery" that is added to the user term. Since this was on 5.0.2.x (a "broken" release evident by the number of hot fixes it received) I had to include some additional changes (config / extension) to avoid the base query being included in the URL hash or being overridden due to pubSub events.

(This use case / change is somewhere on my TODO-list for revisting and preparing a PR. Currently I am a bit busy elsewhere...)

ddraper
Intermediate II

Yes, I would agree that customizing the search list is a better approach - but as I explained it's not that straightforward at the moment, part of the aim of this post was to highlight the thought process... we should fix the AlfSearchList to make this easier, but on the current release the approach described works. 

Also, technically a service is only a singleton at a specific pubSubScope - you can have multiple instances of the same service of the same type if they are configured with different pubSubScope attributes - not necessarily relevant to this solution, but just mentioning as an FYI.

afaust
Master

I hadn't realized the singleton-status being tied to the pubSubScope as well as the service name. But then some of the modules / component can't easily be configured to use different pubSubScopes for different operations. Best example would be alfresco/renderers/Actions which forces a uniform pubSubScope on all actions even if I might want to use an alternative service with a different pubSubScope for individual actions. Some events are always published to global by the widget / module implementation anyway, so you can't use a parallel service with a different configuration.

Interesting information nonetheless - I'll have to consider it some more to see if there is a practical case for using that kind of approach without side-effects / complications.

ddraper
Intermediate II

Again, I don't disagree with that... but as always, we need to know the use cases to do something about them. This is why I'm going to make more of an effort working through forum questions - it's already uncovered a couple of bugs so hopefully will be a fruitful exercise.