department-of-veterans-affairs/vets-website

View on GitHub
src/applications/resources-and-support/hooks/useGetSearchResults.js

Summary

Maintainability
B
4 hrs
Test Coverage
// Node modules.
import { useEffect, useState } from 'react';
import { orderBy } from 'lodash';
// Relative imports.
import recordEvent from 'platform/monitoring/record-event';
import { SEARCH_IGNORE_LIST } from '../constants';

const countKeywordsFound = (searchableString = '', keyword = '') => {
  let keywordsFound = 0;

  // Change the searchable string into a list of words.
  const searchableWords = searchableString.toLowerCase()?.split(' ');

  // Iterate over each word and see if we have any keywords that match it.
  // eslint-disable-next-line no-unused-expressions
  searchableWords?.forEach(word => {
    if (keyword === word) {
      keywordsFound += 1;
    }
  });

  // Return the total number of keywords found.
  return keywordsFound;
};

const countWholePhraseMatchesFound = (searchableString = '', keyword = '') =>
  searchableString.toLowerCase()?.split(keyword.toLowerCase())?.length - 1;

export default function useGetSearchResults(articles, query, page) {
  const [results, setResults] = useState([]);

  // Refresh the results list when the query is submitted or the page is changed.
  useEffect(
    () => {
      if (!articles || !query) {
        return;
      }

      // Begin filtering logic.
      // =====
      const keywords = query
        .split(' ')
        .filter(word => !!word)
        .map(keyword => keyword.toLowerCase())
        .filter(word => !SEARCH_IGNORE_LIST.includes(word))
        .map(keyword => {
          if (keyword.length > 6 && keyword.endsWith('ies')) {
            // Unpluralize the word, so that a search for "disabilities"
            // will still yield articles titled "disability"
            return keyword.slice(0, keyword.length - 3);
          } else if (keyword.length > 3 && keyword.endsWith('s')) {
            // Unpluralize the word, so that a search for "claims"
            // will still yield articles titled "claim or appeal status"
            return keyword.slice(0, keyword.length - 1);
          }
          return keyword;
        });

      let filteredArticles = articles.filter(article => {
        const articleTitleWords = article.title.toLowerCase().split(' ');

        return keywords.some(keyword => {
          return articleTitleWords.some(titleWord =>
            titleWord.startsWith(keyword),
          );
        });
      });
      // =====
      // End of filtering logic.

      // Begin ordering logic.
      // =====
      let orderedResults = [];

      filteredArticles = filteredArticles?.map(article => {
        // Derive keywords counts.
        const keywordsCountsTitle = keywords?.reduce(
          (keywordInstances, keyword) =>
            keywordInstances + countKeywordsFound(article.title, keyword),
          0,
        );
        const keywordsCountsIntroText = keywords?.reduce(
          (keywordInstances, keyword) =>
            keywordInstances + countKeywordsFound(article.introText, keyword),
          0,
        );
        const keywordsCountsContent = keywords?.reduce(
          (keywordInstances, keyword) =>
            keywordInstances +
            countKeywordsFound(article.searchableContent, keyword),
          0,
        );

        // Derive whole phrase match counts.
        const wholePhraseMatchCountsTitle = countWholePhraseMatchesFound(
          article.title,
          query,
        );
        const wholePhraseMatchCountsIntroText = countWholePhraseMatchesFound(
          article.introText,
          query,
        );
        const wholePhraseMatchCountsContent = countWholePhraseMatchesFound(
          article.searchableContent,
          query,
        );

        return {
          ...article,

          // Number of times a keyword is found in the article's title.
          keywordsCountsTitle,

          // Number of times a keyword is found in the article's introText.
          keywordsCountsIntroText,

          // Number of times a keyword is found in the article's searchableContent.
          keywordsCountsContent,

          // Number of times a keyword is found in both the article's introText and searchableContent.
          keywordsCountsIntroTextAndContent:
            keywordsCountsIntroText + keywordsCountsContent,

          // Number of times the full query is found in the article's title.
          wholePhraseMatchCountsTitle,

          // Number of times the full query is found in the article's introText.
          wholePhraseMatchCountsIntroText,

          // Number of times the full query is found in the article's searchableContent.
          wholePhraseMatchCountsContent,

          // Number of times the full query is found in the article's title, introText, searchableContent combined.
          wholePhraseMatchCountsTotal:
            wholePhraseMatchCountsTitle +
            wholePhraseMatchCountsIntroText +
            wholePhraseMatchCountsContent,
        };
      });

      // Sort first by the number of exact query matches (ignoring casing) in the title, introText, and searchableContent descending
      // Sort ties by query word instances found in title descending
      // Sort ties then by query word instances found in introText and searchableContent descending
      // Sort ties then by alphabetical descending
      orderedResults = orderBy(
        filteredArticles,
        [
          'wholePhraseMatchCountsTotal',
          'keywordsCountsTitle',
          'keywordsCountsIntroTextAndContent',
          'title',
        ],
        ['desc', 'desc', 'desc', 'asc'],
      );
      // =====
      // End of ordering logic.

      // Track R&S search results.
      recordEvent({
        event: 'view_search_results',
        'search-page-path': document.location.pathname,
        'search-query': query,
        'search-results-total-count': orderedResults.length,
        'search-results-total-pages': Math.ceil(orderedResults.length / 10),
        'search-selection': 'Resources and support',
        'search-typeahead-enabled': false,
        'search-location': 'Resources And Support',
        'sitewide-search-app-used': false, // this is not the sitewide search app
        'type-ahead-option-keyword-selected': undefined,
        'type-ahead-option-position': undefined,
        'type-ahead-options-list': undefined,
        'type-ahead-options-count': undefined,
      });

      setResults(orderedResults);
    },
    [articles, setResults, query, page],
  );

  return [results];
}