From 661b56641807d3fde5d595522043bb6bd88f795a Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Wed, 27 Mar 2024 13:39:57 +0000 Subject: [PATCH 1/7] temp --- .../grapher/src/core/GrapherAnalytics.ts | 3 ++- site/SiteAnalytics.ts | 9 +++++++++ site/search/Autocomplete.tsx | 16 +++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts b/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts index d557334786a..9037b743bb3 100644 --- a/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts +++ b/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts @@ -19,6 +19,7 @@ export enum EventCategory { KeyboardShortcut = "owid.keyboard_shortcut", SiteClick = "owid.site_click", SiteError = "owid.site_error", + SiteSearchClick = "owid.site_search_click", } enum EventAction { @@ -38,7 +39,7 @@ type countrySelectorEvent = interface GAEvent { event: EventCategory eventAction?: string - eventContext?: string + eventContext?: string | Record eventTarget?: string grapherPath?: string } diff --git a/site/SiteAnalytics.ts b/site/SiteAnalytics.ts index 6039a412092..2aaca06c715 100644 --- a/site/SiteAnalytics.ts +++ b/site/SiteAnalytics.ts @@ -23,4 +23,13 @@ export class SiteAnalytics extends GrapherAnalytics { eventContext: query, }) } + + logSearchClick(query: string, position: string, url: string) { + this.logToGA({ + event: EventCategory.SiteSearchClick, + eventAction: "click", + eventContext: { query, position }, + eventTarget: url, + }) + } } diff --git a/site/search/Autocomplete.tsx b/site/search/Autocomplete.tsx index 6a5ae89cd91..c4a3e6b5927 100644 --- a/site/search/Autocomplete.tsx +++ b/site/search/Autocomplete.tsx @@ -6,7 +6,7 @@ import { autocomplete, getAlgoliaResults, } from "@algolia/autocomplete-js" -import algoliasearch from "algoliasearch" +import algoliasearch, { SearchClient } from "algoliasearch" import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches" import { PageType, @@ -54,6 +54,16 @@ const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ const searchClient = algoliasearch(ALGOLIA_ID, ALGOLIA_SEARCH_KEY) +declare global { + interface Window { + algoliasearch: SearchClient + } +} + +if (typeof window !== "undefined") { + window.algoliasearch = searchClient +} + // This is the same simple function for the two non-Algolia sources const onSelect: AutocompleteSource["onSelect"] = ({ navigator, @@ -155,8 +165,8 @@ const AlgoliaSource: AutocompleteSource = { index === SearchIndexName.Charts ? "Chart" : index === SearchIndexName.Explorers - ? "Explorer" - : pageTypeDisplayNames[item.type as PageType] + ? "Explorer" + : pageTypeDisplayNames[item.type as PageType] return (
Date: Wed, 27 Mar 2024 17:37:28 -0400 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=8E=89=20add=20GTM=20event=20for=20se?= =?UTF-8?q?arch=20results=20click?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@ourworldindata/utils/src/Util.ts | 15 ++++++-- packages/@ourworldindata/utils/src/index.ts | 1 + site/SiteAnalytics.ts | 17 +++++++-- site/search/SearchPanel.tsx | 38 ++++++++++++++++++--- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index 0fcea199c28..d6d0bbb04d4 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1066,8 +1066,8 @@ export function getClosestTimePairs( sortedTimesB[closestIndexInB] < timeA ? closestIndexInB : closestIndexInB > indexB - ? closestIndexInB - 1 - : undefined + ? closestIndexInB - 1 + : undefined /** * the index that holds the value that is definitely equal to or greater than timeA, the candidate time @@ -1831,3 +1831,14 @@ export function cartesian(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) +} diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index f14c7459a2d..577f58ad587 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -118,6 +118,7 @@ export { checkIsDataInsight, checkIsAuthor, cartesian, + isElementHidden, } from "./Util.js" export { diff --git a/site/SiteAnalytics.ts b/site/SiteAnalytics.ts index 2aaca06c715..f346294559e 100644 --- a/site/SiteAnalytics.ts +++ b/site/SiteAnalytics.ts @@ -1,4 +1,5 @@ import { GrapherAnalytics, EventCategory } from "@ourworldindata/grapher" +import { SearchCategoryFilter } from "./search/searchTypes.js" export class SiteAnalytics extends GrapherAnalytics { logCountryProfileSearch(country: string) { @@ -24,11 +25,23 @@ export class SiteAnalytics extends GrapherAnalytics { }) } - logSearchClick(query: string, position: string, url: string) { + 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 }, + eventContext: { query, position, positionInSection, filter }, eventTarget: url, }) } diff --git a/site/search/SearchPanel.tsx b/site/search/SearchPanel.tsx index 1482fe17ec5..336805ed375 100644 --- a/site/search/SearchPanel.tsx +++ b/site/search/SearchPanel.tsx @@ -6,6 +6,7 @@ import { getWindowQueryParams, get, mapValues, + isElementHidden, } from "@ourworldindata/utils" import { InstantSearch, @@ -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 ( @@ -281,22 +285,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) { + const href = target.getAttribute("href") + const query = getWindowQueryParams().q + + if ( + objectId && + positionInSection && + index && + href && + query + ) { logSiteSearchClick({ index, queryID, objectIDs: [objectId], - positions: [parseInt(position)], + positions: [parseInt(positionInSection)], + }) + siteAnalytics.logSearchClick({ + query, + position: String(globalPosition), + positionInSection, + url: href, + filter: activeCategoryFilter, }) } } } }, - [queryID] + [queryID, activeCategoryFilter] ) useEffect(() => { document.addEventListener("click", handleHitClick) From 002ec3501819bbbed8b12d1698d25700efee064f Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Wed, 27 Mar 2024 17:59:44 -0400 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=8E=89=20add=20GTM=20event=20for=20se?= =?UTF-8?q?arch=20tab=20switch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@ourworldindata/grapher/src/core/GrapherAnalytics.ts | 1 + site/SiteAnalytics.ts | 8 ++++++++ site/search/SearchPanel.tsx | 1 + 3 files changed, 10 insertions(+) diff --git a/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts b/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts index 9037b743bb3..80f1803cdf1 100644 --- a/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts +++ b/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts @@ -20,6 +20,7 @@ export enum EventCategory { SiteClick = "owid.site_click", SiteError = "owid.site_error", SiteSearchClick = "owid.site_search_click", + SiteSearchFilterClick = "owid.site_search_filter_click", } enum EventAction { diff --git a/site/SiteAnalytics.ts b/site/SiteAnalytics.ts index f346294559e..0e7beb702ce 100644 --- a/site/SiteAnalytics.ts +++ b/site/SiteAnalytics.ts @@ -45,4 +45,12 @@ export class SiteAnalytics extends GrapherAnalytics { eventTarget: url, }) } + + logSearchFilterClick({ key }: { key: string }) { + this.logToGA({ + event: EventCategory.SiteSearchFilterClick, + eventAction: "click", + eventContext: key, + }) + } } diff --git a/site/search/SearchPanel.tsx b/site/search/SearchPanel.tsx index 336805ed375..0a04dacdd57 100644 --- a/site/search/SearchPanel.tsx +++ b/site/search/SearchPanel.tsx @@ -504,6 +504,7 @@ export class InstantSearchContainer extends React.Component { behavior: "smooth", }) } + siteAnalytics.logSearchFilterClick({ key }) this.setActiveCategoryFilter(key) } From d42d1241c38749eb129db67fe6bd1c0b4ba8429c Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Wed, 27 Mar 2024 19:00:26 -0400 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=8E=89=20add=20instantsearch=20HTM=20?= =?UTF-8?q?event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../grapher/src/core/GrapherAnalytics.ts | 1 + site/SiteAnalytics.ts | 17 +++++++++++++++++ site/search/Autocomplete.tsx | 8 ++++++++ 3 files changed, 26 insertions(+) diff --git a/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts b/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts index 80f1803cdf1..3720bab652d 100644 --- a/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts +++ b/packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts @@ -21,6 +21,7 @@ export enum EventCategory { SiteError = "owid.site_error", SiteSearchClick = "owid.site_search_click", SiteSearchFilterClick = "owid.site_search_filter_click", + SiteInstantSearchClick = "owid.site_instantsearch_click", } enum EventAction { diff --git a/site/SiteAnalytics.ts b/site/SiteAnalytics.ts index 0e7beb702ce..d8837ddb625 100644 --- a/site/SiteAnalytics.ts +++ b/site/SiteAnalytics.ts @@ -46,6 +46,23 @@ export class SiteAnalytics extends GrapherAnalytics { }) } + 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, diff --git a/site/search/Autocomplete.tsx b/site/search/Autocomplete.tsx index c4a3e6b5927..6dc74b5bd76 100644 --- a/site/search/Autocomplete.tsx +++ b/site/search/Autocomplete.tsx @@ -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 @@ -117,6 +120,11 @@ const AlgoliaSource: AutocompleteSource = { 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 }) { From 93eea4d2ad75cb0938a8d8e187042ba4bf30440d Mon Sep 17 00:00:00 2001 From: Ike Saunders Date: Wed, 27 Mar 2024 19:03:52 -0400 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=94=A8=20remove=20testing=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/search/Autocomplete.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/site/search/Autocomplete.tsx b/site/search/Autocomplete.tsx index 6dc74b5bd76..2ea6b7d425b 100644 --- a/site/search/Autocomplete.tsx +++ b/site/search/Autocomplete.tsx @@ -6,7 +6,7 @@ import { autocomplete, getAlgoliaResults, } from "@algolia/autocomplete-js" -import algoliasearch, { SearchClient } from "algoliasearch" +import algoliasearch from "algoliasearch" import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches" import { PageType, @@ -57,16 +57,6 @@ const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({ const searchClient = algoliasearch(ALGOLIA_ID, ALGOLIA_SEARCH_KEY) -declare global { - interface Window { - algoliasearch: SearchClient - } -} - -if (typeof window !== "undefined") { - window.algoliasearch = searchClient -} - // This is the same simple function for the two non-Algolia sources const onSelect: AutocompleteSource["onSelect"] = ({ navigator, From fbbffeb6f856b2e375e9bdf451e99e54def7105e Mon Sep 17 00:00:00 2001 From: ikesau Date: Wed, 27 Mar 2024 23:20:57 +0000 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=A4=96=20style:=20prettify=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@ourworldindata/utils/src/Util.ts | 4 ++-- site/search/Autocomplete.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index d6d0bbb04d4..880efa71643 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1066,8 +1066,8 @@ export function getClosestTimePairs( sortedTimesB[closestIndexInB] < timeA ? closestIndexInB : closestIndexInB > indexB - ? closestIndexInB - 1 - : undefined + ? closestIndexInB - 1 + : undefined /** * the index that holds the value that is definitely equal to or greater than timeA, the candidate time diff --git a/site/search/Autocomplete.tsx b/site/search/Autocomplete.tsx index 2ea6b7d425b..9fe5271441d 100644 --- a/site/search/Autocomplete.tsx +++ b/site/search/Autocomplete.tsx @@ -163,8 +163,8 @@ const AlgoliaSource: AutocompleteSource = { index === SearchIndexName.Charts ? "Chart" : index === SearchIndexName.Explorers - ? "Explorer" - : pageTypeDisplayNames[item.type as PageType] + ? "Explorer" + : pageTypeDisplayNames[item.type as PageType] return (
Date: Tue, 2 Apr 2024 16:22:44 -0400 Subject: [PATCH 7/7] =?UTF-8?q?=E2=9C=A8=20GTM=20search=20events=20code=20?= =?UTF-8?q?review=20enhancements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/search/SearchPanel.tsx | 10 ++++++---- site/search/searchClient.ts | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/site/search/SearchPanel.tsx b/site/search/SearchPanel.tsx index 0a04dacdd57..e11b8ac7df6 100644 --- a/site/search/SearchPanel.tsx +++ b/site/search/SearchPanel.tsx @@ -43,7 +43,7 @@ import { faHeartBroken, faSearch } from "@fortawesome/free-solid-svg-icons" import { DEFAULT_SEARCH_PLACEHOLDER, getIndexName, - logSiteSearchClick, + logSiteSearchClickToAlgoliaInsights, } from "./searchClient.js" import { PreferenceType, @@ -258,6 +258,7 @@ interface SearchResultsProps { activeCategoryFilter: SearchCategoryFilter isHidden: boolean handleCategoryFilterClick: (x: SearchCategoryFilter) => void + query: string } const SearchResults = (props: SearchResultsProps) => { @@ -300,7 +301,7 @@ const SearchResults = (props: SearchResultsProps) => { ) const index = target.getAttribute("data-algolia-index") const href = target.getAttribute("href") - const query = getWindowQueryParams().q + const query = props.query if ( objectId && @@ -309,7 +310,7 @@ const SearchResults = (props: SearchResultsProps) => { href && query ) { - logSiteSearchClick({ + logSiteSearchClickToAlgoliaInsights({ index, queryID, objectIDs: [objectId], @@ -326,7 +327,7 @@ const SearchResults = (props: SearchResultsProps) => { } } }, - [queryID, activeCategoryFilter] + [queryID, activeCategoryFilter, props.query] ) useEffect(() => { document.addEventListener("click", handleHitClick) @@ -555,6 +556,7 @@ export class InstantSearchContainer extends React.Component { { } } -export const logSiteSearchClick = ( +export const logSiteSearchClickToAlgoliaInsights = ( event: Omit ) => { const client = getInsightsClient()