Engenharia

Como desenvolver rapidamente excelentes experiências de pesquisa no React

15 de novembro de 2019: O código desta publicação foi atualizado para incluir as chaves ({ }) que estavam faltando. O fluxo também foi ajustado para evitar erros de tempo de execução.

O desenvolvimento de experiências de pesquisa não é tarefa fácil. Pode até parecer fácil à primeira vista: desenvolver uma barra de pesquisa, inserir dados em um banco de dados e fazer com que o usuário insira consultas em confronto com o banco de dados. Mas existem muitos aspectos a levar em consideração na modelagem de dados, na lógica subjacente e — evidentemente — no design e na experiência do usuário em geral.

Vamos orientá-lo sobre como desenvolver experiências de pesquisa excelentes baseadas no React usando a biblioteca open source Search UI da Elastic. Esse processo levará uns 30 minutos, e depois você estará prontinho para inserir o recurso de pesquisa em qualquer aplicativo que precisar dele.

Mas antes de mais nada, cabe a pergunta: o que é que torna o desenvolvimento de pesquisa um grande desafio?

A pesquisa é um negócio complicado

Há algumas semanas foi publicado um excelente artigo com o título Falsehoods Programmers Believe About Search (Mentiras em que os programadores acreditam sobre a pesquisa). Ele contém uma extensa lista de pressuposições falsas que os desenvolvedores consideram no desenvolvimento da pesquisa.

Estas são algumas dentre muitas mentiras:

  • “Clientes que sabem o que estão procurando buscarão do jeito que você espera.”
  • “É possível escrever um analisador de consultas que sempre analisará a consulta com êxito.”
  • “Uma vez configurada, a pesquisa funcionará da mesma maneira na semana seguinte.”
  • “Os sinônimos são fáceis.”
  • …e muitas outras pérolas — é melhor ler para crer!

A conclusão disso é que a pesquisa apresenta muitos desafios — e alguns até são evidentes. É preciso pensar sobre como gerenciar o estado, desenvolver componentes para filtragem, faceting, classificação, paginação, sinônimos, processamento de idiomas e muito mais. Mas, resumindo:

Desenvolver uma excelente pesquisa consiste em duas partes sofisticadas: (1) o mecanismo de pesquisa, que fornece as APIs para turbinar a pesquisa; e (2) a biblioteca de pesquisa, que incrementa a experiência de pesquisa.

Para o mecanismo de pesquisa, vamos analisar o Elastic App Search.

Para a experiência de pesquisa, vamos apresentar uma biblioteca de pesquisa de sistema operacional: o Search UI.

Quando concluirmos, ela terá uma aparência semelhante a esta:

image2.png

Mecanismo de pesquisa: Elastic App Search

O App Search está disponível como um serviço gerenciado pago ou como uma distribuição autogerenciada gratuita. Vamos usar o serviço gerenciado para este tutorial, mas lembre-se de que a sua equipe poderá usar o Search UI e o App Search com uma licença básica sem custo se você o hospedar por conta própria.

O plano é este: indexar documentos que representam os melhores videogames de todos os tempos em um mecanismo de pesquisa para então projetar e otimizar uma experiência de pesquisa deles.

Primeiro, inscreva-se em uma avaliação de 14 dias — não é necessário ter cartão de crédito.

Crie um Engine (Mecanismo). Você pode escolher entre 13 idiomas diferentes.

Vamos dar o nome de video-games e definir o idioma como English (inglês).

image4.png

Faça download do conjunto de dados dos melhores videogames e faça upload dele para o App Search usando o importador.

Clique em Engine (Mecanismo) e selecione a guia Credentials (Credenciais).

Crie uma nova Public Search Key (Chave de pesquisa pública) com Limited Engine Access (Acesso ao mecanismo limitado) somente ao mecanismo video-games.

Recupere a nova Public Search Key (Chave de pesquisa pública) e seu Host Identifier (Identificador host).

Sei que não está muito parecido, mas eu garanto que agora temos um mecanismo de pesquisa totalmente funcional e pronto para buscar os nossos dados de videogames usando uma API de pesquisa refinada.

Vamos recapitular o que fizemos até o momento:

  • Criamos um mecanismo de pesquisa
  • Fizemos ingestão dos documentos
  • Criamos um esquema padrão
  • Recuperamos uma credencial descartável com escopo que podemos expor ao navegador

E é isso em relação ao App Search, por enquanto.

Agora vamos começar o desenvolvimento de nossa experiência de pesquisa usando o Search UI.

Biblioteca de pesquisa: Search UI

Vamos usar o utilitário de estrutura create-react-app para criar um aplicativo do React:

npm install -g create-react-app
create-react-app video-game-search --use-npm
cd video-game-search

Dentro dessa estrutura, vamos instalar o Search UI e o conector do App Search:

npm install --save @elastic/react-search-ui @elastic/search-ui-app-search-connector

E vamos iniciar o aplicativo em modo de desenvolvimento:

npm start

Abra src/App.js no seu editor de texto favorito.

Vamos começar com algum código padronizado e desempacotá-lo.

Cuidado com os comentários!

// Etapa nº 1: importar declarações
import React from "react";
import AppSearchAPIConnector from "@elastic/search-ui-app-search-connector";
import { SearchProvider, Results, SearchBox } from "@elastic/react-search-ui";
import { Layout } from "@elastic/react-search-ui-views";
import "@elastic/react-search-ui-views/lib/styles/styles.css";
// Etapa nº 2: o conector
const connector = new AppSearchAPIConnector({
  searchKey: "[YOUR_SEARCH_KEY]",
  engineName: "video-games",
  hostIdentifier: "[YOUR_HOST_IDENTIFIER]"
});
// Etapa nº 3: opções de configuração
const configurationOptions = {
  apiConnector: connector
  // Vamos preencher isto juntos.
};
// Etapa nº 4: SearchProvider: os retoques finais
export default function App() {
  return (
    <SearchProvider config={configurationOptions}>
      <div className="App">
        <Layout
        // Vamos preencher isto juntos.
        />
      </div>
    </SearchProvider>
  );
}

Etapa nº 1: importar declarações

Vamos precisar importar as nossas dependências do Search UI e o React.

Os componentes principais, o conector e os componentes de visualização estão contidos em três pacotes diferentes:

  • @elastic/search-ui-app-search-connector
  • @elastic/react-search-ui
  • @elastic/react-search-ui-views

Saberemos mais sobre cada um deles à medida que prosseguirmos.

import React from "react";
import AppSearchAPIConnector from "@elastic/search-ui-app-search-connector";
import { SearchProvider, Results, SearchBox } from "@elastic/react-search-ui";
import { Layout } from "@elastic/react-search-ui-views";

Também importaremos uma folha de estilos padrão para este projeto, a qual nos fornecerá uma aparência bacana sem escrever uma única linha do nosso próprio CSS:

import "@elastic/react-search-ui-views/lib/styles/styles.css";

Etapa 2: o conector

Temos nossa Public Search Key e Host Identifier do App Search.

É hora de colocá-los para trabalhar!

O objeto de conector no Search UI usa as credenciais para se vincular ao App Search e turbinar a pesquisa:

const connector = new AppSearchAPIConnector({
  searchKey: "[YOUR_SEARCH_KEY]",
  engineName: "video-games",
  hostIdentifier: "[YOUR_HOST_IDENTIFIER]"
});

O Search UI funciona com qualquer API de pesquisa. Mas os conectores fazem com que uma API de pesquisa apenas funcione, sem qualquer configuração mais avançada.

Etapa 3: configurationOptions

Antes de mergulharmos em configurationOptions, vamos refletir por alguns momentos.

Importamos um conjunto de dados para o nosso mecanismo de pesquisa. Mas que tipo de dados são esses?

Quanto mais sabemos sobre nossos dados, melhor entendemos como apresentá-los aos usuários da pesquisa. E isso informará como configurar a experiência de pesquisa.

Vamos analisar um objeto, o melhor objeto de todos nesse conjunto de dados:

{ 
  "id":"final-fantasy-vii-ps-1997",
  "name":"Final Fantasy VII",
  "year":1997,
  "platform":"PS",
  "genre":"Role-Playing",
  "publisher":"Sony Computer Entertainment",
  "global_sales":9.72,
  "critic_score":92,
  "user_score":9,
  "developer":"SquareSoft",
  "image_url":"https://r.hswstatic.com/w_907/gif/finalfantasyvii-MAIN.jpg"
}

Vemos que ele apresenta vários campos de texto como name, year, platform e assim por diante, além de alguns campos de número como critic_score, global_sales e user_score.

Se fizermos três perguntas essenciais, saberemos o suficiente para desenvolver uma experiência de pesquisa sólida:

  • Como a maioria das pessoas buscará? Pelo nome do videogame.
  • O que a maioria das pessoas vai querer ver em um resultado? O nome do videogame, o gênero, a criadora, as pontuações e a plataforma.
  • Como a maioria das pessoas fará filtragem, classificação e faceting? Por pontuação, gênero, criadora e plataforma.

Poderemos converter essas respostas nas nossas configurationOptions:

const configurationOptions = {
  apiConnector: connector,
  searchQuery: {
    search_fields: {
      // 1. Busque por nome de videogame.
      name: {}
    },
    // 2. Resultados: nome, gênero, criadora, pontuações e plataforma.
    result_fields: {
      name: {
        // Um snippet significa que os termos de correspondência serão envolvidos em marcas <em>.
        snippet: {
          size: 75, // Limit the snippet to 75 characters.
          fallback: true // Fallback to a "raw" result.
        }
      },
      genre: {
        snippet: {
          size: 50,
          fallback: true
        }
      },
      publisher: {
        snippet: {
          size: 50,
          fallback: true
        }
      },
      critic_score: {
        // As pontuações são numéricas, por isso não aplicaremos snippet.
        raw: {}
      },
      user_score: {
        raw: {}
      },
      platform: {
        snippet: {
          size: 50,
          fallback: true
        }
      },
      image_url: {
        raw: {}
      }
    },
    // 3. Faça faceting por pontuações, gênero, criadora e plataforma, que usaremos para desenvolver filtros mais tarde.
    facets: {
      user_score: {
        type: "range",
        ranges: [
          { from: 0, to: 5, name: "Not good" },
          { from: 5, to: 7, name: "Not bad" },
          { from: 7, to: 9, name: "Pretty good" },
          { from: 9, to: 10, name: "Must play!" }
        ]
      },
      critic_score: {
        type: "range",
        ranges: [
          { from: 0, to: 50, name: "Not good" },
          { from: 50, to: 70, name: "Not bad" },
          { from: 70, to: 90, name: "Pretty good" },
          { from: 90, to: 100, name: "Must play!" }
        ]
      },
      genre: { type: "value", size: 100 },
      publisher: { type: "value", size: 100 },
      platform: { type: "value", size: 100 }
    }
  }
};

Conectamos o Search UI ao nosso mecanismo de pesquisa e agora temos opções que regulam a maneira como vamos buscar pelos dados, exibir nossos resultados e explorar esses resultados. Mas precisamos de alguma coisa para vincular tudo aos componentes de front-end dinâmicos do Search UI.

Etapa 4: SearchProvider

Esse é o objeto que regula tudo. O SearchProvider é o espaço em que todos os outros componentes são aninhados.

O Search UI fornece um componente Layout, que é usado para incrementar um típico layout de pesquisa. Existem opções de personalização avançada, mas não trataremos delas neste tutorial.

Vamos fazer duas coisas:

  1. Repassar as configurationOptions para o SearchProvider.
  2. Colocar alguns blocos de desenvolvimento estruturais no Layout e adicionar dois componentes básicos: SearchBox e Results.
export default function App() {
  return (
    <SearchProvider config={configurationOptions}>
      <div className="App">
        <Layout
          header={<SearchBox />}
          // titleField é o campo mais proeminente em um resultado: o cabeçalho de resultado.
          bodyContent={<Results titleField="name" urlField="image_url" />}
        />
      </div>
    </SearchProvider>
  );
}

Neste ponto, temos o básico configurado no front-end. Há mais alguns detalhes para trabalhar no back-end antes que possamos executar isso. Precisamos também trabalhar no modelo de relevância para que a busca receba um ajuste fino para as necessidades especiais deste projeto.

Acabamos o App Search...

De volta ao laboratório

O App Search conta com recursos de mecanismo de pesquisa poderosos e refinados. Isso torna o ajuste antes sofisticado bem mais agradável. Podemos fazer ajustes de relevância em granularidade fina e alterações de esquema perfeitos com apenas alguns cliques. 

Vamos ajustar o esquema primeiro para vê-lo em ação.

Faça login no App Search, insira o Engine video-games e clique em Schema (Esquema) na seção Manage (Gerenciar).

O esquema aparecerá. Cada um dos 11 campos é considerado text por padrão.

No objeto configurationOptions, definimos duas facetas de intervalo para nos ajudar a buscar nos números: user_score e critic_score. Para que uma faceta de intervalo funcione como esperado, o tipo de campo precisa ser um número.

Clique no menu suspenso ao lado de cada campo, altere-o para number e clique em Update Types (Tipos de atualização):

image1.png

O mecanismo será reindexado imediatamente. E mais tarde — quando adicionarmos os componentes de faceting ao nosso layout — os filtros de intervalo funcionarão como esperado. E agora, vamos para o aspecto realmente bacana.

Esta seção é altamente relevante

Existem três recursos de relevância essenciais: Synonyms, Curations e Relevance Tuning (Sinônimos, Curadorias e Ajuste de relevância).

Selecione cada recurso na seção Search Settings (Configurações de pesquisa) na barra lateral:

image8.png

Synonyms

Algumas pessoas dirigem carros, outras automóveis, outras podem dirigir um pau-velho. A Internet é global e pessoas no mundo inteiro usam palavras diferentes para descrever coisas. Os sinônimos ajudam a criar conjuntos de termos considerados como um único.

No caso de um mecanismo de pesquisa de videogames, sabemos que as pessoas vão querer encontrar Final Fantasy. Mas talvez elas possam digitar FF.

Clique em Synonyms e selecione Create a Synonym Set (Criar um conjunto de sinônimos) e digite os termos:

image6.png

Clique em Save (Salvar). Você pode adicionar quantos conjuntos de sinônimos quiser.

As buscas por FF agora terão o mesmo peso das buscas por Final Fantasy.

Curations

As curadorias são um favorito. E se alguém buscar por Final Fantasy ou FF? Há muitos jogos na série — quais vão aparecer?

Por padrão, os cinco resultados mais procurados são estes:

1. Final Fantasy VIII

2. Final Fantasy X

3. Final Fantasy Tactics

4. Final Fantasy IX

5. Final Fantasy XIII

Isso não parece correto... o Final Fantasy VII foi o melhor jogo de Final Fantasy de todos. E o Final Fantasy XIII não foi muito legal! 😜

Será que podemos fazer com que alguém que busque por Final Fantasy receba Final Fantasy VII como primeiro resultado? E será que podemos remover Final Fantasy XIII dos nossos resultados de pesquisa?

Sim, podemos!

Clique em Curations e insira uma consulta: Final Fantasy.

Arraste o documento Final Fantasy VII até a seção Promoted Documents (Documentos promovidos) agarrando a alça do lado extremo esquerdo da tabela. E clique no botão Hide Result (Ocultar resultado) no documento Final Fantasy XIII — o olho com a linha atravessada:

image7.png

Qualquer um que busque por Final Fantasy ou FF agora verá primeiro Final Fantasy VII.

E não verá Final Fantasy XIII. Há-há!

Podemos promover e ocultar muitos documentos. Podemos inclusive classificar documentos promovidos para mantermos total controle sobre o que aparece na parte superior de cada consulta.

Relevance tuning

Clique em Relevance Tuning na barra lateral.

Buscamos em um campo de texto: o campo name. Mas e se tivermos vários campos de texto que as pessoas busquem, como um campo name e um campo description? O conjunto de dados de videogames que estamos usando não contém um campo de descrição, então vamos adulterar alguns documentos para pensar melhor nisso.

Suponha que os nossos documentos tenham esta aparência:

{ 
  "name":"Magical Quest",
  "description": "A dangerous journey through caves and such." 
},
{ 
  "name":"Dangerous Quest",
  "description": "A magical journey filled with magical magic. Highly magic." 
}

Se alguém quisesse encontrar o jogo Magical Quest, inseriria ele como consulta. Mas o primeiro resultado seria Dangerous Quest:

image3.png

Por quê? Porque a palavra “magical” está presente três vezes na descrição de Dangerous Quest, e o mecanismo de pesquisa não saberá que um campo é mais importante do que o outro. Então ele classificará Dangerous Quest em nível mais alto. É por conta desse impasse que existe o Relevance Tuning.

Podemos selecionar um campo e — entre outras coisas — aumentar o peso de sua relevância:

image5.gif

Vemos que, ao escalonar para cima o peso, o item correto — Magical Quest — sobe para o topo porque o campo name fica mais significativo. Tudo o que precisamos fazer é arrastar o controle deslizante para um valor maior e clicar em Save (Salvar).

Agora usamos o App Search para:

  • Ajustar o esquema e alterar user_score e critic_score para campos de number (número).
  • Fazer ajuste fino no modelo de relevância.

E com isso concluímos os excelentes recursos de “painel” — cada um tem um endpoint de API correspondente que pode ser usado para fazer tudo funcionar via programação se você não gostar de GUIs.

Agora, vamos concluir a IU.

Retoques finais

Neste ponto, sua UI deve estar funcional. Experimente fazer algumas consultas e explore. A primeira coisa que notamos é que faltam ferramentas para explorar nossos resultados, como filtragem, faceting, classificação e assim por diante, mas que a busca funciona. Precisaremos aprimorar a UI.

No arquivo src/App.js inicial, importamos três componentes básicos:

import { SearchProvider, Results, SearchBox } from "@elastic/react-search-ui";

Vamos adicionar mais alguns, considerando o que definimos para as nossas opções de configuração.

A importação dos seguintes componentes permitirá que as habilidades ausentes na IU:

  • PagingInfo: Exibam informações sobre a página atual.
  • ResultsPerPage: Configurem quantos resultados aparecem em cada página.
  • Paging: Naveguem por páginas diferentes.
  • Facet: Filtrem e explorem dados de maneiras exclusivas para o tipo de dados.
  • Sorting: Reorientem os resultados para um determinado campo.
import {
  PagingInfo,
  ResultsPerPage,
  Paging,
  Facet,
  SearchProvider,
  Results,
  SearchBox,
  Sorting
} from "@elastic/react-search-ui";

Depois de importados, os componentes podem ser colocados no Layout.

O componente Layout divide a página em seções, e os componentes podem ser colocados nessas seções via props.

Ele contém seções para:

  • header: a caixa/barra de pesquisa
  • bodyContent: o container de resultados
  • sideContent: a barra lateral, que contém facets e opções de classificação
  • bodyHeader: o “wrapper” em torno dos resultados com informações ricas em contexto como página atual e número de resultados por página
  • bodyFooter: as opções de páginas para rápida navegação entre páginas

Dados de renderização de componentes. Os dados são buscados com base nas configurações de pesquisa que fornecemos em configurationOptions. Agora, vamos colocar cada componente na seção Layout apropriada.

Por exemplo, descrevemos cinco dimensões de faceting em configurationOptions, por isso vamos criar cinco componentes Facet. Cada componente Facet usará um prop "field" como chave de volta aos nossos dados.

Vamos colocá-los na seção sideContent, juntamente com o nosso componente Sorting e colocar os componentes Paging, PagingInfo e ResultsPerPage nas seções mais adequadas:

<Layout
  header={<SearchBox />}
  bodyContent={<Results titleField="name" urlField="image_url" />}
  sideContent={
    <div>
      <Sorting
        label={"Sort by"}
        sortOptions={[
          {
            name: "Relevance",
            value: "",
            direction: ""
          },
          {
            name: "Name",
            value: "name",
            direction: "asc"
          }
        ]}
      />
      <Facet field="user_score" label="User Score" />
      <Facet field="critic_score" label="Critic Score" />
      <Facet field="genre" label="Genre" />
      <Facet field="publisher" label="Publisher" isFilterable={true} />
      <Facet field="platform" label="Platform" />
    </div>
  }
  bodyHeader={
    <>
      <PagingInfo />
      <ResultsPerPage />
    </>
  }
  bodyFooter={<Paging />}
/>

Agora vamos analisar a experiência de pesquisa no ambiente de desenvolvimento local.

Agora está bem melhor! Temos opções sofisticadas para explorar os resultados de buscas.

Acrescentamos alguns recursos como várias opções de classificação, e tornamos o facet da criadora filtrável adicionando um único sinalizador. Experimente uma pesquisa com uma consulta em branco e explore todas as opções.

Por fim… vamos analisar um último recurso da experiência de pesquisa. É um bastante conhecido...

O autocompletar.

Você me autocompleta

Os usuários de pesquisa adoram o recurso de autocompletar porque ele fornece resultados instantâneos. Suas sugestões vêm em duas versões: result e query. Dependendo da versão, um usuário de pesquisa receberá resultados relevantes ou potenciais consultas que podem gerar resultados.

Vamos nos concentrar no recurso de autocompletar como forma de sugestão da consulta.

Isso exige duas alterações rápidas.

Primeiro, precisamos adicionar o autocompletar ao objeto configurationOptions:

const configurationOptions = {
  autocompleteQuery: {
    suggestions: {
      types: {
        documents: {
          // Quais campos buscar por sugestões
          fields: ["name"]
        }
      },
      // Quantas sugestões aparecem
      size: 5
    }
  },
  ...
};

Segundo, precisamos habilitar o recurso autocompletar como função de SearchBox:

...
        <Layout
          ...
          header={<SearchBox autocompleteSuggestions={true} />}
/>
...

Yep — that’s it.

Tente buscar — à medida que você digita, aparecem sugestões de consulta de autocompletar.

Resumo

Agora temos uma experiência de pesquisa funcional de boa aparência. E evitamos toda uma confusão de armadilhas em que as pessoas geralmente caem ao tentar implementar o recurso de pesquisa. Nada mal para 30 minutos, não acha?

O Search UI é uma moderna e flexível estrutura do React para o rápido desenvolvimento de experiências de pesquisa. O Elastic App Search é um mecanismo de pesquisa robusto desenvolvido sobre o Elasticsearch. Trata-se de um serviço gerenciado pago ou você pode executá-lo por conta própria gratuitamente com uma ampla licença básica.

Vamos adorar ver o que você desenvolve com o Search UI. Acesse o Gitter e avalie dar uma colaboração ao projeto!