I’ve been wrestling with SuiteCRM global search for years and have never quite conquered it, until now.
I’ve created a couple of pull requests to fix a couple of bugs I ran into:
- [ElasticSearch Indexing batch error handling by 2xaronl · Pull Request #9844 · salesagility/SuiteCRM · GitHub](ElasticSearch Indexing batch error handling by 2xaronl · Pull Request #9844 · salesagility/SuiteCRM · GitHub
- https://github.com/salesagility/SuiteCRM/pull/9845
I also had issues with custom module I had added an ‘address’ field to (which actually adds a few fields with prefab naming). The ‘address’ field (street address) was throwing some ‘expected an object but got a value’ (or something like that) errors when trying to index. Apparently the indexing script makes some incorrect assumptions about fields named ‘address’. Instead of tracking down the bug I just created a new ‘address_line1’ column and manually copied the data over from the ‘address’ column before removing the ‘address’ field.
In addition, I’ve modified lib/Search/ElasticSearch/ElasticSearchEngine.php as such:
private function createSearchParams(SearchQuery $query): array
{
$searchStr = $query->getSearchString();
$searchModules = SearchWrapper::getModules();
$indexes = implode(',', array_map('strtolower', $searchModules));
// Wildcard character required for Elasticsearch
$wildcardBe = "*";
// Override frontend wildcard character
if (isset($GLOBALS['sugar_config']['search_wildcard_char'])) {
$wildcardFe = $GLOBALS['sugar_config']['search_wildcard_char'];
if ($wildcardFe !== $wildcardBe && strlen($wildcardFe) === 1) {
$searchStr = str_replace($wildcardFe, $wildcardBe, $searchStr);
}
}
// Add wildcard at the beginning of the search string
if (isset($GLOBALS['sugar_config']['search_wildcard_infront']) &&
$GLOBALS['sugar_config']['search_wildcard_infront'] === true && $searchStrPt1[0] !== $wildcardBe) {
$searchStrPt1 = $wildcardBe . $searchStr;
}
// Add wildcard at the end of search string
if ((substr_compare($searchStrPt1, $wildcardBe, -strlen($wildcardBe))) !== 0) {
$searchStrPt1 .= $wildcardBe;
}
// 2xaronl Modifications 20221208
$searchStrPt1 = str_replace(" ", $wildcardBe, $searchStrPt1);
$searchStrPt2 = str_replace(" ", "~1 AND ", $searchStr);
if ((substr_compare($searchStrPt2, "~1", -strlen("~1"))) !== 0) {
$searchStrPt2 .= "~1";
}
$searchStrPt3 = str_replace(" ", "", $searchStr);
$searchStrFinal = "(" . $searchStrPt1 . ")^5 OR (" . $searchStrPt2 . ") OR (" . $searchStrPt3 . ")^3";
return [
'index' => $indexes,
'body' => [
'stored_fields' => [],
'from' => $query->getFrom(),
'size' => $query->getSize(),
'query' => [
'query_string' => [
'query' => $searchStrFinal, // 2xaronl Modification 20221208
'fields' => ['name.*^3', 'email.*^4', 'phone.*^7','address.*^10', '*'],
'analyzer' => 'standard',
'default_operator' => 'OR',
'minimum_should_match' => '66%',
],
],
],
];
}
I applied the changes above because the out-of-the-box ElasticSearch query just wasn’t cutting it. My company is service-based tech company and so addresses, phone numbers, and email addresses needed to score higher so I added some boosts to those terms in the ‘fields’ string. I also found that searching for ‘Some Company’ might turn up the Account but not much else. By adding/modifying the query terms above it generates the following query when searching for ‘Some Company’:
[body] => Array
(
[stored_fields] => Array
(
)
[from] => 0
[size] => 15
[query] => Array
(
[query_string] => Array
(
[query] => (*Some*Company*)^5 OR (Some~1 AND Company~1) OR (SomeCompany)^3
[fields] => Array
(
[0] => name.*^3
[1] => email.*^4
[2] => phone.*^7
[3] => address.*^10
[4] => *
)
[analyzer] => standard
[default_operator] => OR
[minimum_should_match] => 66%
)
)
)
The three terms help find not only exact matches for the search but also email addresses, common misspellings, and common data entry issues.
I’ve only been using this search strategy for about 6 business hours so far but users seem pretty happy with the results and the tests I’ve run against it have been great. I really like how I can apply different weighting to the search terms and fields but it would be nice if there was a way to do this within the config files (because what I’ve currently got is not upgrade-safe). Perhaps someone can find a way to implement ‘elasticsearch query pattern’ and ‘elasticsearch query fields’ configuration parameters that are flexible enough (or adopt something similar to the code above so I don’t need to re-apply my ‘fix’ each time I upgrade).
I hope someone else finds this useful!
Note: I’m currently on SuiteCRM 7.12.6 but I’ve diff’d the above code against the current code in github and it’s still applicable).