Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(search): pre-select explorer entities if they appear in search query #3483

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion baker/algolia/configureAlgolia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ALGOLIA_INDEXING,
ALGOLIA_SECRET_KEY,
} from "../../settings/serverSettings.js"
import { countries } from "@ourworldindata/utils"
import { countries, regions } from "@ourworldindata/utils"
import { SearchIndexName } from "../../site/search/searchTypes.js"
import { getIndexName } from "../../site/search/searchClient.js"

Expand All @@ -25,6 +25,11 @@ export const getAlgoliaClient = (): SearchClient | undefined => {
return client
}

const allCountryNamesAndVariants = regions.flatMap((region) => [
region.name,
...(("variantNames" in region && region.variantNames) || []),
])

// This function initializes and applies settings to the Algolia search indices
// Algolia settings should be configured here rather than in the Algolia dashboard UI, as then
// they are recorded and transferrable across dev/prod instances
Expand Down Expand Up @@ -164,6 +169,7 @@ export const configureAlgolia = async () => {
attributeForDistinct: "explorerSlug",
distinct: 4,
minWordSizefor1Typo: 6,
optionalWords: allCountryNamesAndVariants,
})

const synonyms = [
Expand Down
120 changes: 86 additions & 34 deletions site/search/SearchPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
sortBy,
groupBy,
uniqBy,
EntityName,
Url,
Region,
} from "@ourworldindata/utils"
import {
InstantSearch,
Expand Down Expand Up @@ -61,6 +64,7 @@ import {
import {
DEFAULT_GRAPHER_HEIGHT,
DEFAULT_GRAPHER_WIDTH,
setSelectedEntityNamesParam,
} from "@ourworldindata/grapher"
import type { SearchResults as AlgoliaSearchResultsType } from "algoliasearch-helper"
import { SiteAnalytics } from "../SiteAnalytics.js"
Expand Down Expand Up @@ -95,6 +99,22 @@ function PagesHit({ hit }: { hit: IPageHit }) {
)
}

const getEntityQueryStr = (
entities: EntityName[] | null | undefined,
existingQueryStr: string = ""
) => {
if (!entities?.length) return existingQueryStr
else {
return setSelectedEntityNamesParam(
// If we have any entities pre-selected, we want to show the chart tab
Url.fromQueryStr(existingQueryStr).updateQueryParams({
tab: "chart",
}),
entities
).queryStr
}
}

function ChartHit({ hit }: { hit: IChartHit }) {
const [imgLoaded, setImgLoaded] = useState(false)
const [imgError, setImgError] = useState(false)
Expand Down Expand Up @@ -155,7 +175,11 @@ interface GroupedExplorerViews {
const getNumberOfExplorerHits = (rawHits: IExplorerViewHit[]) =>
uniqBy(rawHits, "explorerSlug").length

function ExplorerViewHits() {
function ExplorerViewHits({
countriesRegionsToSelect,
}: {
countriesRegionsToSelect?: Region[]
}) {
const { hits } = useHits<IExplorerViewHit>()

const groupedHits = useMemo(() => {
Expand Down Expand Up @@ -193,6 +217,7 @@ function ExplorerViewHits() {
groupedHit={group}
key={group.explorerSlug}
cardPosition={i}
countriesRegionsToSelect={countriesRegionsToSelect}
/>
))}
</div>
Expand All @@ -203,14 +228,25 @@ function ExplorerViewHits() {
function ExplorerHit({
groupedHit,
cardPosition,
countriesRegionsToSelect,
}: {
groupedHit: GroupedExplorerViews
cardPosition: number
countriesRegionsToSelect?: Region[]
}) {
const firstHit = groupedHit.views[0]

// If the explorer title contains something like "Ukraine" already, don't bother selecting Ukraine in it
const entitiesToSelectExcludingExplorerTitle =
countriesRegionsToSelect?.filter(
(e) => !groupedHit.explorerTitle.includes(e.name)
)
const queryStr = getEntityQueryStr(
entitiesToSelectExcludingExplorerTitle?.map((e) => e.name)
)

const exploreAllProps = {
href: `${BAKED_BASE_URL}/${EXPLORERS_ROUTE_FOLDER}/${groupedHit.explorerSlug}`,
href: `${BAKED_BASE_URL}/${EXPLORERS_ROUTE_FOLDER}/${groupedHit.explorerSlug}${queryStr}`,
"data-algolia-index": getIndexName(SearchIndexName.ExplorerViews),
"data-algolia-object-id": firstHit.objectID,
"data-algolia-position": firstHit.hitPositionOverall,
Expand Down Expand Up @@ -242,38 +278,52 @@ function ExplorerHit({
</a>
</div>
<ul className="search-results__explorer-views-list grid grid-cols-2 grid-sm-cols-1">
{groupedHit.views.map((view) => (
<li
key={view.objectID}
className="ais-Hits-item search-results__explorer-view"
>
<a
data-algolia-index={getIndexName(
SearchIndexName.ExplorerViews
)}
data-algolia-object-id={view.objectID}
data-algolia-position={view.hitPositionOverall + 1}
data-algolia-card-position={cardPosition + 1}
data-algolia-position-within-card={
view.hitPositionWithinCard + 1
}
data-algolia-event-name="click_explorer_view"
href={`${BAKED_BASE_URL}/${EXPLORERS_ROUTE_FOLDER}/${view.explorerSlug}${view.viewQueryParams}`}
className="search-results__explorer-view-title-container"
{groupedHit.views.map((view) => {
const entitiesToSelectExcludingViewTitle =
entitiesToSelectExcludingExplorerTitle?.filter(
(e) =>
!view.viewTitle.includes(e.name) &&
!view.explorerTitle.includes(e.name)
)
const queryStr = getEntityQueryStr(
entitiesToSelectExcludingViewTitle?.map((e) => e.name),
view.viewQueryParams
)
return (
<li
key={view.objectID}
className="ais-Hits-item search-results__explorer-view"
>
<Highlight
attribute="viewTitle"
hit={view}
highlightedTagName="strong"
className="search-results__explorer-view-title"
/>
<FontAwesomeIcon icon={faArrowRight} />
</a>
<p className="body-3-medium-italic search-results__explorer-view-subtitle">
{view.viewSubtitle}
</p>
</li>
))}
<a
data-algolia-index={getIndexName(
SearchIndexName.ExplorerViews
)}
data-algolia-object-id={view.objectID}
data-algolia-position={
view.hitPositionOverall + 1
}
data-algolia-card-position={cardPosition + 1}
data-algolia-position-within-card={
view.hitPositionWithinCard + 1
}
data-algolia-event-name="click_explorer_view"
href={`${BAKED_BASE_URL}/${EXPLORERS_ROUTE_FOLDER}/${view.explorerSlug}${queryStr}`}
className="search-results__explorer-view-title-container"
>
<Highlight
attribute="viewTitle"
hit={view}
highlightedTagName="strong"
className="search-results__explorer-view-title"
/>
<FontAwesomeIcon icon={faArrowRight} />
</a>
<p className="body-3-medium-italic search-results__explorer-view-subtitle">
{view.viewSubtitle}
</p>
</li>
)
})}
</ul>
<a
className="search-results__explorer-hit-link-mobile hide-sm-up"
Expand Down Expand Up @@ -639,7 +689,9 @@ const SearchResults = (props: SearchResultsProps) => {
) => getNumberOfExplorerHits(results.hits)}
/>
</header>
<ExplorerViewHits />
<ExplorerViewHits
countriesRegionsToSelect={searchQueryRegionsMatches}
/>
</section>
</NoResultsBoundary>
</Index>
Expand Down