От автора: этот пост представляет собой компиляцию утвержденных технических решений, применяемых благодаря React и Elastic Search и связанных с созданием виджета поиска с использованием автозаполнения со следующими требованиями ниже.
Создать панель поиска для быстрого нахождения клиента по имени или фамилии или по уникальному короткому коду.
Он должен поддерживаться поиск по принципу «поступательный-набор» с уточнением результатов при нажатии новых клавиш
Поиск должен быть возможен по первому, второму имени, фамилии или по полному имени.
Если существует два или более клиентов с одинаковым именем, сортировать их по времени создания, последний созданный клиент должен размещаться выше.
Создание этого виджета автозаполнения разделяется на две части. Давайте сначала поговорим о разработке back-end.
Совет. При работе с компонентами React вы можете использовать Bit, чтобы легко организовывать и обмениваться компонентами между проектами и членами команды и ускорить процесс разработки.
API ElasticSearch
Хотя многие RDBM уже поддерживают полнотекстовый поиск, мы предпочли бы ElasticSearch из-за его превосходного алгоритма оценки и производительности. Существует два способа создания индекса ElasticSearch для обеспечения функциональности автоматического предложения:
1. Предложение заполнения
Предложение заполнения — это так называемое префикс-предложение. Оно не выполняет коррекцию орфографии, как предложение term или phrase, но позволяет использовать базовые функции автозаполнения. Если производительность является основной задачей, рекомендуется обратиться к предложения заполнения.
2. Tokenizer NGram Edge
Согласно приведенным требованиям результаты должны выводиться даже в том случае, если пользователь вводит второго имени клиента. Это невозможно с помощью предложения заполнения, поэтому давайте использовать Tokenizer NGram.
Если пользователь вводит chandler или muriel или bing или chand bing, мы предположим, что в результате получим Chandler Muriel Bing!
Tokenizer NGram сначала разбивает имя на слова каждый раз, когда встречает один из списка указанных символов, затем он создает N-граммы каждого слова, где начало N-граммы привязывается к началу слова. Это идеально, когда индекс должен соответствовать полным или частичным вводимым ключевым словам из имени.
Если вы хотите развернуть локальный Docker images, чтобы загрузить Elastic Search и поэкспериментировать с кодом из этого поста, перейдите по этой ссылке. Код соответствует ES v.6.4, который является версией LTS на момент написания этого поста.
Давайте продолжим и создадим индекс с полями name и short_code:
PUT http://localhost:9200/crm_app { "settings": { "index": { "analysis": { "filter": {}, "analyzer": { "analyzer_keyword": { "tokenizer": "keyword", "filter": "lowercase" }, "edge_ngram_analyzer": { "filter": [ "lowercase" ], "tokenizer": "edge_ngram_tokenizer" } }, "tokenizer": { "edge_ngram_tokenizer": { "type": "edge_ngram", "min_gram": 2, "max_gram": 5, "token_chars": [ "letter" ] } } } } }, "mappings": { "customers": { "properties": { "fullName": { "type": "text", "analyzer": "edge_ngram_analyzer" }, "shortCode": { "type": "text" }, "createdDate": { "type": "date" } } } } }
PUT API для создания нового индекса (ElasticSearch v.6.4)
Чтобы узнать больше о параметрах min_gram и max_gram, обратитесь к документации Edge NGram. Также обратите внимание, что мы создаем одно поле с именем fullName для объединения имени и фамилии клиента. Сохранение имени и фамилии как одного поля дает нам большую гибкость в плане анализа и запросов.
Данные клиентов
Предполагая, что у нас есть данные клиента в качестве образца, мы можем вставить их в индекс Elastic Search, используя следующую команду:
POST http://localhost:9200/crm_app/customers { "fullName": "Rachel Karen Green", "shortCode": "RKG10", "createdDate": "2018-10-10" }
Повторите приведенную выше команду для всех записей клиентов, и после этого мы можем начать тестирование индекса на соответствие нашим критериям. Теперь давайте начнем поэтапно строить API запросов.
POST http://localhost:9200/crm_app/customers/_search { "query": { "match": { "fullName": "geller" } }
Этот запрос вернет две записи Ross и Monica. Давайте отсортируем их по дате создания. Но нам нужно не только задать порядок отображения таким образом, чтобы более новые результаты перемещались в верхнюю часть списка. Правильный способ отображения будет — сортироваться по рейтингу, а затем по релевантности.
POST http://localhost:9200/crm_app/customers/_search { "query": { "match": { "fullName": "geller" } }, "sort": ["_score", {"createdDate": "desc"}] }
Помните о том, что для каждой записи, исходя из запроса на совпадение, elastic search присваивает _score на основе релевантности совпадения, и сортировка по этому критерию дает нам наиболее точные результаты поиска. Теперь последнее требование — добавить к реализации поиск по fullName, а также по shortCode, чтобы запрос работал для обоим полям.
POST http://localhost:9200/crm_app/customers/_search { "query": { "multi_match": { "query": "CMB14", "fields": ["fullName", "shortCode"] } }, "sort": ["_score", {"createdDate": "desc"}] }
Запрос multi_match соответствует заданному ключевому слову запроса для обоих полей и соответственно возвращает результат.
React UI для автозаполнения
Давайте бегло рассмотрим виджет пользовательского интерфейса, который мы строим, чтобы вам было проще его визуализировать:
Ниже показан основной виджет AutoComplete, в котором происходит большая часть действий.
import React from 'react' import ReactDOM from 'react-dom' import Autosuggest from 'react-autosuggest' import axios from 'axios' import { debounce } from 'throttle-debounce' import './styles.css' class AutoComplete extends React.Component { state = { value: '', suggestions: [] } componentWillMount() { this.onSuggestionsFetchRequested = debounce( 500, this.onSuggestionsFetchRequested ) } renderSuggestion = suggestion => { return ( <div className="result"> <div>{suggestion.fullName}</div> <div className="shortCode">{suggestion.shortCode}</div> </div> ) } onChange = (event, { newValue }) => { this.setState({ value: newValue }) } onSuggestionsFetchRequested = ({ value }) => { axios .post('http://localhost:9200/crm_app/customers/_search', { query: { multi_match: { query: value, fields: ['fullName', 'shortCode'] } }, sort: ['_score', { createdDate: 'desc' }] }) .then(res => { const results = res.data.hits.hits.map(h => h._source) this.setState({ suggestions: results }) }) } onSuggestionsClearRequested = () => { this.setState({ suggestions: [] }) } render() { const { value, suggestions } = this.state const inputProps = { placeholder: 'customer name or short code', value, onChange: this.onChange } return ( <div className="App"> <h1>AutoComplete Demo</h1> <Autosuggest suggestions={suggestions} onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} onSuggestionsClearRequested={this.onSuggestionsClearRequested} getSuggestionValue={suggestion => suggestion.fullName} renderSuggestion={this.renderSuggestion} inputProps={inputProps} /> </div> ) } }
Быстро пройдемся по коду:
мы используем response-autosuggest, чтобы реализовать основные функции автозаполнения
axios, чтобы выполнить вызов REST API к elastic search
так как пользователь вводит данные с клавиатуры, нам нужно ожидать нажатия клавиш, а затем вызывать elastic search, чтобы сделать опыт пользователя оптимальным и не вызывать поисковый api для каждого слова. Для достижения этой цели мы используем функцию debounce onSuggestionFetchRequested
хелпер renderSuggestion позволит вам сформировать элементы результатов поиск так, как вы хотите их вывести. Мы включили в результат имя клиента и короткий код
Полный код проекта UI доступен здесь: https://github.com/rcdexta/react-autocomplete-demo
Обратите внимание, что UI приложение и ElasticSearch работают на разных портах, поэтому по умолчанию вы получите в браузере ошибку CORS. Для тестирования и разработки используйте следующую команду docker:
$ docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e "http.cors.enabled=true" -e "http.cors.allow-origin=*" docker.elastic.co/elasticsearch/elasticsearch:6.4.0
Автор: RC
Источник: https://blog.bitsrc.io/
Редакция: Команда webformyself.