La création de recherche applicative avec React et Elastic App Search

Lorsqu'un utilisateur fait une recherche, ce qu'il veut, c'est obtenir des résultats pertinents. Mais ce n'est pas tout. Pour le rallier complètement à votre cause, il faut que son expérience de recherche soit conviviale. La recherche doit être rapide, réagir rapidement dès qu'un utilisateur entre des caractères et donner un sentiment d'intelligence et d'efficacité.

Dans ce tutoriel, vous en apprendrez davantage sur la recherche applicative en découvrant comment créer une expérience de recherche fluide et robuste avec React et le client JavaScript d'Elastic App Search. À la fin de ce tutoriel, vous aurez créé une application conviviale, pertinente et React-ive qui vous permettra d'exécuter des recherches sur plusieurs paquets npm en temps réel en procédant à un tri par facette, et dont l'état sera maintenu en tant que partie intégrante de l'URI.

Le code exécuté est accessible sur GitHub.


Bonjour !

Nous avons une bonne nouvelle pour vous ! Vous avez envie de savoir comment mettre en place une recherche encore plus rapide avec Search UI, une bibliothèque open source d'Elastic ? Nous avons un article plus récent sur le sujet.

Lisez-le à la place !


Conditions requises

Pour pouvoir suivre ce tutoriel, vous aurez besoin des éléments suivants :

  1. Une version récente de Node.js
  2. Une version récente de npm
  3. Un compte Elastic App Search Service ou un essai gratuit de 14 jours encore actif
  4. Environ 30 minutes

Présentation d'App Search

Ce qui constitue la base d'une application, ce sont les données. Facebook a provoqué une véritable révolution dans nos cercles sociaux en présentant les données de nos "amis" de façon intéressante. eBay s'est vite imposé comme le moyen le plus simple de trouver et d'acheter des objets de seconde main. Wikipedia a permis aux lecteurs d'en apprendre davantage sur... tous les sujets, qui plus est avec facilité.

Les applications existent pour résoudre des problèmes de données. La recherche est donc un élément phare en la matière. S'il s'agit d'une application répandue, une partie de son offre portera sur la recherche : recherche d'amis, de produits, de conversations, d'articles, etc. Plus l'ensemble de données est vaste et intéressant, plus l'application sera populaire, en particulier si la recherche qu'elle propose offre des résultats pertinents et satisfaisants.

Elastic App Search est adossée à Elasticsearch, un moteur de recherche RESTful, distribué et open source. Avec Elastic App Search, les développeurs accèdent à un ensemble robuste de points de terminaison API optimisé pour prendre en charge des cas d'utilisation de recherche applicative.

Faites tourner vos moteurs

Pour commencer, créez un moteur dans App Search.

Un moteur ingère des objets afin de les indexer. Un objet est une donnée : il peut s'agit du profil d'un ami, d'un produit ou d'une page wiki. Une fois les données ingérées dans App Search, elles sont indexées par rapport à un schéma flexible et optimisées pour la recherche. À partir de là, vous pouvez exploiter différentes bibliothèques client pour mettre en place une expérience de recherche conviviale.

Dans cet exemple, nous appellerons notre moteur node-modules.

Une fois le moteur créé, nous aurons besoin de trois éléments à partir de la page Credentials (Données d'identification) :

  1. l'identificateur de l'hôte, dont le préfixe est host- ;
  2. une clé d'API privée, dont le préfixe est private- ;
  3. une clé de recherche publique, dont le préfixe est search-.

Avec ces trois éléments, nous pouvons cloner le projet, entrer le répertoire, vérifier l'embranchement de départ, puis exécuter une installation npm :

$ git clone https://github.com/swiftype/app-search-demo-react.git
$ cd react-tutorial && git checkout starter && npm install

Super ! Nous avons une application opérationnelle à notre disposition. Mais si nous voulons faire une recherche, nous aurons besoin de données...

Ingestion

Dans la plupart des cas, vos objets se trouveront dans une base de données ou une API back-end. Comme il s'agit ici d'un exemple, nous allons utiliser un fichier .json statique. Le référentiel contient deux scripts : init-data.js et index-data.js. Le premier est un script d'extraction de contenu utilisé pour acquérir des données node-module correctement mises en forme à partir de npm. Les données sont présentes dans le fichier node-modules.json. Le deuxième est un script d'indexation qui va ingérer ces données dans votre moteur App Search pour les indexer.

Pour exécuter le script d'indexation, nous devons également fournir l'identificateur de notre hôte et la clé d'API privée.

$ REACT_APP_HOST_IDENTIFIER={Your Host Identifier} \
REACT_APP_API_KEY={Your Private API Key} \
npm run index-data

Les objets sont rapidement envoyés à notre moteur App Search par lots de 100, tandis que l'index se constitue.

Nous devrions à présent avoir un tableau de bord comptant environ 9 500 paquets npm en tant que documents pour le moteur que nous venons de créer. D'ailleurs, si vous ne l'avez pas encore fait, il peut être utile de jeter un petit œil aux données pour en connaître le contenu.

app_search_engine_overview.png

Réactivité

Maintenant que nous avons mis du "carburant" dans notre moteur et que celui-ci est opérationnel, nous pouvons commencer à créer notre application de base.

$ npm start

Démarrons npm depuis le répertoire de projet pour ouvrir le code standard React. Le style de ce code s'inspire d'App.css. Personnalisons-le pour l'adapter à nos besoins.

À noter : nous aurons bientôt besoin de mettre en place une zone de recherche pour pouvoir y entrer nos requêtes. D'habitude, ces zones très utiles sont rectangulaires et indiquent quelque chose du style : saisissez votre requête ici pour trouver ce que vous recherchez. Et c'est une zone rectangulaire de ce type que les utilisateurs chercheront à coup sûr !

//App.css
...
.App-search-box {
height: 40px;
width: 500px;
font-size: 1em;
margin-top: 10px;
}

Nous devrons aussi placer nos identifiants App Search dans un endroit sécurisé, comme un fichier .env.

Créez un fichier de ce type dans le répertoire racine du projet, et remplissez-le, comme suit :

//.env
REACT_APP_HOST_IDENTIFIER={identificateur de votre hôte, commençant par host-}
REACT_APP_SEARCH_KEY={votre clé de recherche publique, commençant par search-}

Maintenant que nous avons placé ces données en toute sécurité, nous pouvons commencer à écrire notre logique de recherche.

Que la recherche commence !

C'est dans le fichier App.js que se trouve la logique de base. Ce fichier, comme la plupart des autres fichiers de démarrage, a été créé par create-react-application, un outil qui aide à amorcer les applications React sans qu'aucune configuration ne soit nécessaire. Avant que nous écrivions une logique pour tester la recherche, nous devons installer la bibliothèque client Swiftype App Search JavaScript :

$ npm install --save swiftype-app-search-javascript

Indiquez le code suivant dans App.js. Ce code effectuera une recherche simple.

Nous allons coder en dur le terme "foo" comme exemple de recherche :

import * as SwiftypeAppSearch from "swiftype-app-search-javascript";
const client = SwiftypeAppSearch.createClient({
hostIdentifier: process.env.REACT_APP_HOST_IDENTIFIER,
apiKey: process.env.REACT_APP_SEARCH_KEY,
engineName: "node-modules"
});
//Nous pouvons faire une recherche sur n'importe quel terme. Ici, nous utilisons le terme foo comme exemple.
const query = "foo";
const options = {};
client.search(query, options)
.then(resultList => console.log(resultList))
.catch(error => console.log(error))

Le navigateur va s'actualiser et créer un tableau resultList via console.log. Pour explorer ce tableau, nous pouvons ouvrir la console développeur de notre navigateur. Nous pouvons effectuer quelques autres requêtes en remplaçant le terme "foo" par une autre chaîne. Une fois la requête modifiée et la page actualisée, nous pouvons voir comment l'ensemble des résultats s'est adapté.

Parfait ! Avec tous ces éléments, nous faisons déjà des recherches dans notre moteur node-modules.

Vers des résultats pertinents

Nous disposons d'un schéma simple pour faire des recherches, mais les résultats sont peu utiles car ils sont noyés dans le fichier console.log. Ce que nous allons faire à présent, c'est supprimer le style React de base et le code que nous avons mis en place, puis étendre les choses.

Nous allons créer...

  1. une variable d'état détenant une propriété de réponse ;
  2. une méthode performQuery qui interrogera App Search à l'aide de client.search. Elle stockera les résultats de la requête dans la propriété de réponse ;
  3. un hook de cycle de vie componentDidMount qui s'exécutera une fois au chargement de l'application. Nous ferons de nouveau une recherche sur le terme "foo", mais n'importe quel terme peut convenir ;
  4. un HTML structuré pour conserver la sortie obtenue et le nombre total de résultats.
//App.js
// ... Code tronqué
class App extends Component {
state = {
// Une nouvelle propriété d'état, qui détient la réponse la plus récente à la requête
response: null
};
componentDidMount() {
/*Le fait d'appeler "this" dans componentDidMount garantit que les résultats s'affichent
à l'écran lorsque l'appli se charge pour la première fois*/
this.performQuery("foo");
}
// Méthode pour exécuter une requête et stocker la réponse
performQuery = queryString => {
client.search(queryString, {}).then(
response => {
// Ajoutez "this" pour l'instant de sorte à pouvoir examiner la réponse complète
console.log(response);
this.setState({ response });
},
error => {
console.log(`error: ${error}`);
}
);
};
render() {
const {response} = this.state;
if (!response) return null;
return (


Node Module Search



{/*Montre le nombre total de résultats pour cette requête*/}

{response.info.meta.page.total_results} Results


{/*Itère sur les résultats et indique leur nom et leur description*/}
{response.results.map(result => (

Name: {result.getRaw("name")}


Description: {result.getRaw("description")}



))}

);
}
}
// ... Code tronqué

Au moment où nous cliquons sur "Save" (Enregistrer), les résultats s'affichent dans http://localhost:3000 — il y a 27 résultats et quelques modules qui semblent super. Si quelque chose se passe mal, nous pouvons consulter la console étant donné que nous avons deux fichiers console.log dans le code.

Quid de la zone de recherche ?

Nous avons codé "foo" en dur dans nos requêtes. Le plus grand avantage de la recherche, c'est qu'on peut la lancer à partir d'une expression, quelle qu'elle soit. Une fois que vous aurez mis en place une expérience de recherche de qualité, vous pourrez alors optimiser les expressions plus courantes, afin d'organiser les ensembles de résultats les plus pertinents. Tout commence par une page blanche : la zone de recherche.

Pour créer une zone de recherche digne de ce nom, nous allons ajouter une propriété à l'état, appelée queryString. Pour que queryString s'actualise avec les nouvelles chaînes, nous allons créer une méthode updateQuery, nous tirerons parti d'un gestionnaire onChange pour mettre à jour queryString et nous déclencherons une nouvelle recherche à chaque fois que l'utilisateur modifie le texte qui se trouve dans la zone.

Notre classe App se présente désormais de la façon suivante :

//src/App.js
// ... Code tronqué
class App extends Component {
state = {
// Une nouvelle propriété d'état, qui surveille la valeur à partir de la zone de recherche
queryString: "",
response: null
};
componentDidMount() {
// Supprimez la recherche codée en dur pour "node"
this.performQuery(this.state.queryString);
}
// Gère l'événement onChange à chaque fois que l'utilisateur tape dans la zone de recherche.
updateQuery = e => {
const queryString = e.target.value;
this.setState(
{
queryString // Enregistre la chaîne de requête entrée par l'utilisateur
},
() => {
this.performQuery(queryString); // Déclenche une nouvelle recherche
}
);
};
performQuery = queryString => {
client.search(queryString, {}).then(
response => {
this.setState({
response
});
},
error => {
console.log(`error: ${error}`);
}
);
};
render() {
const {response, queryString} = this.state;
if (!response) return null;
return (


Node Module Search



{/*Une zone de recherche, connectée à la valeur de la chaîne de notre requête et au gestionnaire
onChange*/}

);
}
}
// ... Code tronqué

La fonction anti-rebond

Dans cette itération, à chaque fois qu'un changement est détecté dans la zone, une recherche est déclenchée. Or, cela peut demander beaucoup de ressources à nos systèmes. Pour y pallier, nous appliquons une fonction _debounce_, avec l'amabilité de Lodash.

$ npm install --save lodash

L'anti-rebond est une méthode qui consiste à limiter le nombre de requêtes entrantes selon un intervalle défini en millisecondes. Un utilisateur peut réfléchir à la façon de formuler sa requête, faire des fautes ou écrire très vite. Nous n'avons pas besoin de lancer une requête à chaque changement détecté.

En englobant notre méthode performQuery dans une fonction debounce de Lodash, nous pouvons définir une limite de 200 ms, ce qui permet d'attendre que 200 ms s'écoulent sans qu'aucun changement ne soit détecté pour que la nouvelle requête se lance :

//App.js
// ... Code tronqué
import { debounce } from "lodash"; // Importe debounce
// ... Code tronqué
performQuery = debounce(queryString => {
client.search(queryString, {}).then(
response => {
this.setState({
response
});
},
error => {
console.log(`error: ${error}`);
}
);
}, 200); // 200 millisecondes.
// ... Code tronqué

Outre le fait que la définition d'une limite permet à nos serveurs de faire une pause, elle permet de fluidifier les requêtes de l'utilisateur. Rien que cela ! Le confort est important.

Et ensuite ?

C'est le début d'une expérience de recherche React-ive de qualité. Mais nous pouvons l'optimiser encore davantage. Par exemple, nous pourrions en définir le style ou mettre en œuvre des fonctionnalités App Search dynamiques, comme les facettes, les curations ou encore le réglage de la pertinence. Ou nous pourrions utiliser Analytics API Suite pour révéler des informations exploitables sur l'activité de recherche des utilisateurs.

Et si nous voulons vraiment pousser les choses, le fichier README qui se trouve dans l'embranchement principal poursuit sur le sujet en proposant de mettre en place une gestion des états basée sur les URI, ainsi que d'ajouter un style fluide, une pagination, un filtrage et une recherche à facettes. Avec quelques personnalisations stylistiques, nous pouvons jeter les bases d'une expérience de recherche de haute qualité.

Résumé

Par le passé, il était complexe de créer une recherche applicative pertinente et gratifiante. Elastic App Search offre un moyen rapide et contrôlé de mettre en place une recherche évolutive dans les applications web. Et le meilleur ? Que vos compétences techniques soient avérées ou limitées, vous pouvez gérer les fonctionnalités principales au sein d'un tableau de bord fluide et intuitif. Vous pouvez vous faire la main avec App Search grâce à un essai gratuit de 14 jours (et sans coordonnées bancaires).