Skip to content

Commit

Permalink
Merge pull request #3420 from owid/ga-search-analytics
Browse files Browse the repository at this point in the history
Search events in GTM
  • Loading branch information
ikesau authored Apr 2, 2024
2 parents b93215d + cb7c8de commit 33c9a3d
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export enum EventCategory {
KeyboardShortcut = "owid.keyboard_shortcut",
SiteClick = "owid.site_click",
SiteError = "owid.site_error",
SiteSearchClick = "owid.site_search_click",
SiteSearchFilterClick = "owid.site_search_filter_click",
SiteInstantSearchClick = "owid.site_instantsearch_click",
}

enum EventAction {
Expand All @@ -38,7 +41,7 @@ type countrySelectorEvent =
interface GAEvent {
event: EventCategory
eventAction?: string
eventContext?: string
eventContext?: string | Record<string, string>
eventTarget?: string
grapherPath?: string
}
Expand Down
11 changes: 11 additions & 0 deletions packages/@ourworldindata/utils/src/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1831,3 +1831,14 @@ export function cartesian<T>(matrix: T[][]): T[][] {
[[]]
)
}

export function isElementHidden(element: Element | null): boolean {
if (!element) return false
const computedStyle = window.getComputedStyle(element)
if (
computedStyle.display === "none" ||
computedStyle.visibility === "hidden"
)
return true
return isElementHidden(element.parentElement)
}
1 change: 1 addition & 0 deletions packages/@ourworldindata/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export {
checkIsDataInsight,
checkIsAuthor,
cartesian,
isElementHidden,
} from "./Util.js"

export {
Expand Down
47 changes: 47 additions & 0 deletions site/SiteAnalytics.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GrapherAnalytics, EventCategory } from "@ourworldindata/grapher"
import { SearchCategoryFilter } from "./search/searchTypes.js"

export class SiteAnalytics extends GrapherAnalytics {
logCountryProfileSearch(country: string) {
Expand All @@ -23,4 +24,50 @@ export class SiteAnalytics extends GrapherAnalytics {
eventContext: query,
})
}

logSearchClick({
query,
position,
url,
positionInSection,
filter,
}: {
query: string
position: string
positionInSection: string
url: string
filter: SearchCategoryFilter
}) {
this.logToGA({
event: EventCategory.SiteSearchClick,
eventAction: "click",
eventContext: { query, position, positionInSection, filter },
eventTarget: url,
})
}

logInstantSearchClick({
query,
url,
position,
}: {
query: string
url: string
position: string
}) {
this.logToGA({
event: EventCategory.SiteInstantSearchClick,
eventAction: "click",
eventContext: { query, position },
eventTarget: url,
})
}

logSearchFilterClick({ key }: { key: string }) {
this.logToGA({
event: EventCategory.SiteSearchFilterClick,
eventAction: "click",
eventContext: key,
})
}
}
8 changes: 8 additions & 0 deletions site/search/Autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import {
parseIndexName,
} from "./searchClient.js"
import { queryParamsToStr } from "@ourworldindata/utils"
import { SiteAnalytics } from "../SiteAnalytics.js"

const siteAnalytics = new SiteAnalytics()

type BaseItem = Record<string, unknown>

Expand Down Expand Up @@ -107,6 +110,11 @@ const AlgoliaSource: AutocompleteSource<BaseItem> = {
sourceId: "autocomplete",
onSelect({ navigator, item, state }) {
const itemUrl = prependSubdirectoryToAlgoliaItemUrl(item)
siteAnalytics.logInstantSearchClick({
query: state.query,
url: itemUrl,
position: String(state.activeItemId),
})
navigator.navigate({ itemUrl, item, state })
},
getItemUrl({ item }) {
Expand Down
45 changes: 39 additions & 6 deletions site/search/SearchPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getWindowQueryParams,
get,
mapValues,
isElementHidden,
} from "@ourworldindata/utils"
import {
InstantSearch,
Expand Down Expand Up @@ -42,7 +43,7 @@ import { faHeartBroken, faSearch } from "@fortawesome/free-solid-svg-icons"
import {
DEFAULT_SEARCH_PLACEHOLDER,
getIndexName,
logSiteSearchClick,
logSiteSearchClickToAlgoliaInsights,
} from "./searchClient.js"
import {
PreferenceType,
Expand All @@ -52,6 +53,9 @@ import {
DEFAULT_GRAPHER_HEIGHT,
DEFAULT_GRAPHER_WIDTH,
} from "@ourworldindata/grapher"
import { SiteAnalytics } from "../SiteAnalytics.js"

const siteAnalytics = new SiteAnalytics()

function PagesHit({ hit }: { hit: IPageHit }) {
return (
Expand Down Expand Up @@ -254,6 +258,7 @@ interface SearchResultsProps {
activeCategoryFilter: SearchCategoryFilter
isHidden: boolean
handleCategoryFilterClick: (x: SearchCategoryFilter) => void
query: string
}

const SearchResults = (props: SearchResultsProps) => {
Expand Down Expand Up @@ -281,22 +286,48 @@ const SearchResults = (props: SearchResultsProps) => {
const objectId = target.getAttribute(
"data-algolia-object-id"
)
const position = target.getAttribute(

const allVisibleHits = Array.from(
document.querySelectorAll(
".search-results .ais-Hits-item a"
)
).filter((e) => !isElementHidden(e))

// starts from 1 at the top of the page
const globalPosition = allVisibleHits.indexOf(target) + 1
// starts from 1 in each section
const positionInSection = target.getAttribute(
"data-algolia-position"
)
const index = target.getAttribute("data-algolia-index")
if (objectId && position && index) {
logSiteSearchClick({
const href = target.getAttribute("href")
const query = props.query

if (
objectId &&
positionInSection &&
index &&
href &&
query
) {
logSiteSearchClickToAlgoliaInsights({
index,
queryID,
objectIDs: [objectId],
positions: [parseInt(position)],
positions: [parseInt(positionInSection)],
})
siteAnalytics.logSearchClick({
query,
position: String(globalPosition),
positionInSection,
url: href,
filter: activeCategoryFilter,
})
}
}
}
},
[queryID]
[queryID, activeCategoryFilter, props.query]
)
useEffect(() => {
document.addEventListener("click", handleHitClick)
Expand Down Expand Up @@ -474,6 +505,7 @@ export class InstantSearchContainer extends React.Component {
behavior: "smooth",
})
}
siteAnalytics.logSearchFilterClick({ key })
this.setActiveCategoryFilter(key)
}

Expand Down Expand Up @@ -524,6 +556,7 @@ export class InstantSearchContainer extends React.Component {
<SearchResults
isHidden={!this.inputValue}
activeCategoryFilter={this.activeCategoryFilter}
query={this.inputValue}
handleCategoryFilterClick={
this.handleCategoryFilterClick
}
Expand Down
2 changes: 1 addition & 1 deletion site/search/searchClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const parseIndexName = (index: string): SearchIndexName => {
}
}

export const logSiteSearchClick = (
export const logSiteSearchClickToAlgoliaInsights = (
event: Omit<InsightsSearchClickEvent, "eventName">
) => {
const client = getInsightsClient()
Expand Down

0 comments on commit 33c9a3d

Please sign in to comment.