Commit 70eed3d8 authored by Jeroen Opdebeeck's avatar Jeroen Opdebeeck Committed by Robin De Herdt
Browse files

Issue #3318014: improve documentation Elasticsearch - Search API

parent 40471b1d
Loading
Loading
Loading
Loading
+275 −13
Original line number Diff line number Diff line
@@ -2,13 +2,21 @@

## About

This module provides a way to easily created faceted search pages. Depends on search_api and elasticsearch_connector to index content.
This module provides a framework to set up custom Elasticsearch based search pages.

Features:
- Ajax powered faceted search
- Synonyms
- Autocompletion
- Search suggestions
It depends on search_api to manage your indexed data and elasticsearch_connector to set up a connection
with your Elasticsearch backend.

The elasticsearch_search_api module provides services, interfaces, Controllers and Classes to help you set up
custom Elasticsearch search pages, including the option to add facets, modify the search query that is sent to Elastic, add different search suggesters and analysers to fields (for example: n-gram) or add different searching
'strategies' to your Elasticsearch index.

Out-of-the box the module provides the ability to add autosuggestion, did-you-mean functionality,
synonyms and a strategy to copy all your indexed field data into a 'custom_all' text based field.

The idea of this module is to use it as a framework when building your own custom search module.
There is a lot of functionality that is included in the base module, but in your own search implementation,
services, controllers, templates, etc can be extended or extra searching strategies can be added to fit your needs.

## Installation

@@ -31,9 +39,10 @@ Add both this module and the block ui library to your project's composer.json, w
```
And install it as usual: `composer require drupal/elasticsearch_search_api`

### Example module
### Example module elasticsearch_search_api_example

Submodule elasticsearch_search_api_example - should only be used as a demo.
The base module has a submodule, elasticsearch_search_api_example. This submodule is intended as a demo
of a custom elasticsearch implementation or as a reference point when starting your own custom implementation.

#### Installation steps

@@ -47,10 +56,197 @@ Submodule elasticsearch_search_api_example - should only be used as a demo.
To create content to populate the example search, add some "Elasticsearch page" nodes.
Facets are activated, they can be used by creating "page_type" taxonomy terms. These can be referenced on "Elasticsearch page" content.


## Usage

### Routes & Controllers
This module should be used as a starting point for your own custom search implementation. It provides services that handle the communication between
Drupal and the ElasticSearch backend. 

You can take a look at the elasticsearch_search_api_example module as a reference on how to start building your own custom search module.

## Services

The base module has a services.yml.example file. Each custom search implementation should define its own services.yml file and define what services should be used 
on their custom implementation.

#### elasticsearch_search_api.elasticsearch_params_builder

The params builder service builds the parameters for the search action that we send to the elasticsearch backend. This service builds an array that will be converted
to JSON and sent over to the elasticsearch backend.

JSON examples:

Default example query, when not using keywords:
```
GET elasticsearch_index_drupal_default/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "must": []
    }
  },
  "highlight": {
    "fields": {
      "title": {}
    },
    "pre_tags": [
      "<strong>"
    ],
    "post_tags": [
      "</strong>"
    ]
  }
}
```
Example query when using keywords:
```
GET elasticsearch_index_drupal_default/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "filter": {
        "bool": {
          "must": []
        }
      },
      "should": [
        {
          "query_string": {
            "query": "keyword",
            "fields": []
          }
        },
        {
          "nested": {
            "path": "es_attachment",
            "query": {
              "bool": {
                "must": {
                  "query_string": {
                    "query": "keyword",
                    "fields": [
                      "title^1.0"
                    ]
                  }
                }
              }
            }
          }
        }
      ],
      "minimum_should_match": 1
    }
  },
  "highlight": {
    "fields": {
      "es_attachment.attachment.content": {}
    },
    "pre_tags": [
      "<strong>"
    ],
    "post_tags": [
      "</strong>"
    ]
  }
}
```

#### elasticsearch_search_api.search_action_factory
Factory that builds a search action based on HTTP request query parameters.

This service is required by the SearchController.
```
$query = $request->query;
```
```
$searchAction = $this->searchActionFactory->searchActionFromQuery($query, $this->facets, $request->isXmlHttpRequest());
```

It is expected that the request has a 'keyword' parameter, this will be used as the search keyword that will be sent to elasticsearch.
```
  public function searchActionFromQuery(ParameterBag $query, array $facets, bool $isXmlHttpRequest): FacetedKeywordSearchAction {
    $keyword = $query->get('keyword');
```

If the elasticsearch implementation has a faceted search, the facet collection also needs to
be passed from the controller to the searchAction. 

The SearchActionFactory also lets you set the amount of search results per page. This is a parameter that can be altered
in the services.yml file:
```
elasticsearch_search_api.search_page_size: 10
```

#### elasticsearch_search_api.elasticsearch_result_parser
This service parses a raw ElasticSearch response into a SearchResult object.
The SearchResult object is a value object that represents the search results returned by the ElasticSearch backend.

This service is used by the Controller. 

#### elasticsearch_search_api.search_repository
This service is used by the Controller.

It is used to pass the parameters to the client of the elasticsearch cluster and load items
out of the index by id.

For example:
The JSON that is built by the elasticsearch_params_builder service is passed to the client through
the search_repository service.

```
$params = $this->searchParamsBuilder->build($searchAction);
$response = $this->searchRepository->query($params);
```
Or the query that is sent to return suggestions on the 'title' entity field:
```
    $params = [
      'body' => [
        '_source' => 'title',
        'suggest' => [
          'search-suggest' => [
            'prefix' => $searchQuery,
            'completion' => [
              'field' => 'search_suggest',
              'size' => 10,
            ],
          ],
        ],
      ],
    ];

    $response = $this->searchRepository->query($params);
```

#### elasticsearch_search_api.suggest.title_suggester
This service uses the search_repository service to add a suggester to the elasticsearch index settings.

This service adds a suggester for the 'title' field. It is required that your
index at least has a title field with the 'title' as property path in order for this to work.

More information about suggesters in ElasticSearch can be found on:
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html

#### elasticsearch_search_api.term_facet_storage
Storage service to fetch metadata from Drupal taxonomy terms. Used for facet building.

#### elasticsearch_search_api.term_tree_storage
Storage service to fetch metadata from hierarchical Drupal taxonomy terms. Used for hierarchical facet building.

#### elasticsearch_search_api.event_subscriber.initialize_index
Event subscriber that triggers on the PREPARE_INDEX and PREPARE_INDEX_MAPPING events sent out by
the elasticsearch_connector module.

In the base module, this event subscriber is used to add an ngram tokenizer and analyser to the index 
configuration.

By default, this added ngram analyzer is also added to the 'title' (entity title) field.

More info about n-gram tokenizers within ElasticSearch can be found on: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html

## Routes & Controllers

A search results page typically needs two routes/callbacks:
- search callback to render the search page on page loads
@@ -109,7 +305,7 @@ class MyCustomController extends SearchController {
}
```

### Facets
## Facets

Creating and using facets requires the following:
- an instance of `Drupal\elasticsearch_search_api\Search\Facet\Control\CompositeFacetControlInterface` or `Drupal\elasticsearch_search_api\Search\Facet\Control\FacetControlInterface`
@@ -146,9 +342,75 @@ class RegionFacetControl extends TermFacetBase {
  }
```

### ParamsBuilder
## Searching strategies

The base module has a SyncStrategy base class and a SyncService. These are used to add custom
searching strategies to your ElasticSearch index.

The SyncStrategy base class should be extended by your custom search strategies.
The SyncService is used to alter the index settings and field mapping to add these searching strategies
to your index.
```
  public function execute(ClientInterface $client, array $settingsParams = [], array $mappingParams = []) {
    try {
      $client->indices()->close(['index' => $this->indexName]);
      if (!empty($settingsParams)) {
        $client->indices()->putSettings($settingsParams);
      }
      if (!empty($mappingParams)) {
        $client->indices()->putMapping($mappingParams);
      }
      return TRUE;
    }
    catch (\Exception $e) {
      watchdog_exception('elasticsearch_search_api', $e);
      return FALSE;
    }

    finally {
      sleep(1);
      $client->indices()->open(['index' => $this->indexName]);
    }
  }
```


Within the module file there is a hook_cron implementation that will trigger the synchronization of the 
searching strategies with the Elasticsearch index.

This will apply the changes to the index settings and field mappings, and trigger a reïndex of the data.
```
  public function sync() {
    // Update analysis.
    /** @var \Drupal\elasticsearch_search_api\SyncStrategyInterface $strategy */
    foreach ($this->strategies as $strategy) {
      $strategy->execute($this->client);
    }

    // Reindex all items, so synonyms are correctly picked up.
    $this->reindexItems();
  }
```

The base module provides 4 different searching strategies out-of-the-box:

#### Autosuggest
This strategy adds a completion field type named 'search_suggest' to the index.
By default this strategy will alter the indexed 'title' field to copy it's data over to this added
search_suggest field.

#### CustomAll
This strategy will add a 'custom_all' field of type 'text' to the index field mappings.
All supported fields will be altered to have their data copied to this custom_all field.

This strategy can be usefull if you do not want to configure the ParamsBuilder to query each field
individually but instead query the 'custom_all' field.

#### DidYouMean
This strategy adds trigram analyser to the field mappings (n-gram of size 3).
It is added to the title field mapping and can be used by the controller to render a 'Did you mean' 
snippet.

### Sync service
#### Synonoyms
This strategy can be used to add a synonyms graph token filter to the index settings.
More info: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-graph-tokenfilter.html#analysis-synonym-graph-tokenfilter