From 8f70dadd1dc315ef9015fff82e59328cf89e8d5a Mon Sep 17 00:00:00 2001 From: Holly Date: Tue, 17 Sep 2024 14:37:59 -0700 Subject: [PATCH 01/12] Add BACK_TO_TOP event and fire when button is clicked --- frontend/src/components/VScrollButton.vue | 14 ++++++++++++++ frontend/src/types/analytics.ts | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/frontend/src/components/VScrollButton.vue b/frontend/src/components/VScrollButton.vue index 47a4b97cafa..dc984f4886c 100644 --- a/frontend/src/components/VScrollButton.vue +++ b/frontend/src/components/VScrollButton.vue @@ -1,6 +1,10 @@ diff --git a/frontend/src/types/analytics.ts b/frontend/src/types/analytics.ts index 50cb81d34a8..25b35b676a0 100644 --- a/frontend/src/types/analytics.ts +++ b/frontend/src/types/analytics.ts @@ -101,6 +101,20 @@ export type Events = { /** The content type being searched (can include All content) */ searchType: SearchType } + /** + * Description: The user clicks on the "back to top" caret button in the bottom-right of + * the search results page. + */ + BACK_TO_TOP: { + /** The search query */ + query: string + /** The current page of results the user is on. */ + page: string + /** The number of pixels the user has scrolled. */ + scrollPixels: number + /** The maximum number of pixels the user has scrolled on this pageload. */ + maxScroll: number + } /** * Description: Whenever the user scrolls to the end of the results page. * Useful to evaluate how often users load more results or click From 8d0651eb97cde281465cecdd52e09bc55d45b096 Mon Sep 17 00:00:00 2001 From: Holly Date: Tue, 17 Sep 2024 16:57:25 -0700 Subject: [PATCH 02/12] Fix type errors; fix back-to-top behavior --- frontend/src/components/VScrollButton.vue | 11 +++++++---- frontend/src/types/analytics.ts | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/VScrollButton.vue b/frontend/src/components/VScrollButton.vue index dc984f4886c..3fb31ac8cf7 100644 --- a/frontend/src/components/VScrollButton.vue +++ b/frontend/src/components/VScrollButton.vue @@ -22,15 +22,18 @@ const hClass = computed(() => props.isFilterSidebarVisible ? positionWithSidebar : positionWithoutSidebar ) const scrollToTop = (e: MouseEvent) => { - const element = - (e.currentTarget as HTMLElement)?.closest("#main-page") || window + const mainPage = document.getElementById("main-page") + const element = mainPage || window element.scrollTo({ top: 0, left: 0, behavior: "smooth" }) sendCustomEvent("BACK_TO_TOP", { query: searchStore.searchTerm, page: mediaStore.currentPage, - scrollPixels: document.getElementById("main-page").scrollTop, // Sorry! - maxScroll: document.getElementById("main-page").scrollTopMax, + scrollPixels: mainPage?.scrollTop || 0, + maxScroll: + mainPage && "scrollTopMax" in mainPage + ? (mainPage.scrollTopMax as number) + : 0, }) } diff --git a/frontend/src/types/analytics.ts b/frontend/src/types/analytics.ts index 25b35b676a0..3a4d268369b 100644 --- a/frontend/src/types/analytics.ts +++ b/frontend/src/types/analytics.ts @@ -109,11 +109,11 @@ export type Events = { /** The search query */ query: string /** The current page of results the user is on. */ - page: string + page: number /** The number of pixels the user has scrolled. */ - scrollPixels: number + scrollPixels: number | undefined /** The maximum number of pixels the user has scrolled on this pageload. */ - maxScroll: number + maxScroll: number | undefined } /** * Description: Whenever the user scrolls to the end of the results page. From 9913dd858f9b0ad6f04c91eb9914ed2440d18b7c Mon Sep 17 00:00:00 2001 From: Holly! Date: Tue, 17 Sep 2024 17:02:33 -0700 Subject: [PATCH 03/12] Line break for clarity Co-authored-by: zack <6351754+zackkrida@users.noreply.github.com> --- frontend/src/components/VScrollButton.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/VScrollButton.vue b/frontend/src/components/VScrollButton.vue index 3fb31ac8cf7..02b3bcecfe5 100644 --- a/frontend/src/components/VScrollButton.vue +++ b/frontend/src/components/VScrollButton.vue @@ -16,6 +16,7 @@ const props = withDefaults( const { sendCustomEvent } = useAnalytics() const searchStore = useSearchStore() const mediaStore = useMediaStore() + defineEmits<{ tab: [KeyboardEvent] }>() const hClass = computed(() => From 3469e9e64ceb28dc0b0b49a9aea3e174372573b3 Mon Sep 17 00:00:00 2001 From: Holly! Date: Tue, 17 Sep 2024 17:03:51 -0700 Subject: [PATCH 04/12] Remove unused variable Co-authored-by: zack <6351754+zackkrida@users.noreply.github.com> --- frontend/src/components/VScrollButton.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/VScrollButton.vue b/frontend/src/components/VScrollButton.vue index 02b3bcecfe5..974f7b4d9e9 100644 --- a/frontend/src/components/VScrollButton.vue +++ b/frontend/src/components/VScrollButton.vue @@ -22,7 +22,7 @@ defineEmits<{ tab: [KeyboardEvent] }>() const hClass = computed(() => props.isFilterSidebarVisible ? positionWithSidebar : positionWithoutSidebar ) -const scrollToTop = (e: MouseEvent) => { +const scrollToTop = (_: MouseEvent) => { const mainPage = document.getElementById("main-page") const element = mainPage || window element.scrollTo({ top: 0, left: 0, behavior: "smooth" }) From b1f5000e44ad2692072e13a649769a40a915743f Mon Sep 17 00:00:00 2001 From: Holly! Date: Wed, 18 Sep 2024 13:29:05 -0700 Subject: [PATCH 05/12] Remove unused parameter Co-authored-by: Olga Bulat --- frontend/src/components/VScrollButton.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/VScrollButton.vue b/frontend/src/components/VScrollButton.vue index 974f7b4d9e9..10702ce5f6d 100644 --- a/frontend/src/components/VScrollButton.vue +++ b/frontend/src/components/VScrollButton.vue @@ -22,7 +22,7 @@ defineEmits<{ tab: [KeyboardEvent] }>() const hClass = computed(() => props.isFilterSidebarVisible ? positionWithSidebar : positionWithoutSidebar ) -const scrollToTop = (_: MouseEvent) => { +const scrollToTop = () => { const mainPage = document.getElementById("main-page") const element = mainPage || window element.scrollTo({ top: 0, left: 0, behavior: "smooth" }) From 34eaf2ea684bfd170730b0949476ef4ab68eb72f Mon Sep 17 00:00:00 2001 From: Holly Date: Thu, 19 Sep 2024 14:04:07 -0700 Subject: [PATCH 06/12] Use in favor of useAnalytics, which is deprecated --- frontend/src/components/VScrollButton.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/VScrollButton.vue b/frontend/src/components/VScrollButton.vue index 10702ce5f6d..dbddfc911b5 100644 --- a/frontend/src/components/VScrollButton.vue +++ b/frontend/src/components/VScrollButton.vue @@ -1,7 +1,7 @@ From c72e1c87225b11124e4534a1d3a5f62c7b6bf8df Mon Sep 17 00:00:00 2001 From: Holly Date: Fri, 20 Sep 2024 13:42:41 -0700 Subject: [PATCH 08/12] Update params to match REACH_RESULT_END --- frontend/src/components/VScrollButton.vue | 21 ++++++++++++++++++--- frontend/src/types/analytics.ts | 14 +++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/VScrollButton.vue b/frontend/src/components/VScrollButton.vue index 30b92aa7c48..d87a3cc1942 100644 --- a/frontend/src/components/VScrollButton.vue +++ b/frontend/src/components/VScrollButton.vue @@ -5,6 +5,9 @@ import { useNuxtApp, useRoute } from "#imports" import { useSearchStore } from "~/stores/search" import { useMediaStore } from "~/stores/media" +import type { ResultKind } from "~/types/result" +import type { Collection } from "~/types/search" + const positionWithoutSidebar = "ltr:right-4 rtl:left-4" const positionWithSidebar = "ltr:right-[22rem] rtl:left-[22rem]" @@ -37,14 +40,26 @@ const scrollToTop = () => { element.scrollTo({ top: 0, left: 0, behavior: "smooth" }) if (ANALYTICS_ROUTES.some((r) => route.name?.toString().startsWith(r))) { + const kind: ResultKind = + searchStore.strategy === "default" ? "search" : "collection" + const collectionType = (searchStore.collectionValue as Collection) ?? "null" + $sendCustomEvent("BACK_TO_TOP", { + searchType: + searchStore.searchType === "model-3d" || + searchStore.searchType === "video" + ? "all" + : searchStore.searchType, + kind, query: searchStore.searchTerm, - page: mediaStore.currentPage, - scrollPixels: mainPage?.scrollTop || 0, + resultPage: mediaStore.currentPage, + scrollPixels: mainPage?.scrollTop || -1, maxScroll: mainPage && "scrollTopMax" in mainPage ? (mainPage.scrollTopMax as number) - : 0, + : -1, + collectionType, + collectionValue: searchStore.collectionValue ?? "null", }) } } diff --git a/frontend/src/types/analytics.ts b/frontend/src/types/analytics.ts index 3a4d268369b..f7aa7242297 100644 --- a/frontend/src/types/analytics.ts +++ b/frontend/src/types/analytics.ts @@ -106,15 +106,19 @@ export type Events = { * the search results page. */ BACK_TO_TOP: { - /** The search query */ + /** The media type being searched */ + searchType: SupportedSearchType + /** The kind of search reached (a collection, a standard search view, etc.) */ + kind: ResultKind + /** The search term */ query: string /** The current page of results the user is on. */ - page: number + resultPage: number /** The number of pixels the user has scrolled. */ - scrollPixels: number | undefined + scrollPixels: number /** The maximum number of pixels the user has scrolled on this pageload. */ - maxScroll: number | undefined - } + maxScroll: number + } & CollectionProperties /** * Description: Whenever the user scrolls to the end of the results page. * Useful to evaluate how often users load more results or click From e310352624a793184398fb98ef547397cc85fd4e Mon Sep 17 00:00:00 2001 From: Holly Date: Fri, 20 Sep 2024 13:48:51 -0700 Subject: [PATCH 09/12] Run eslint --- frontend/src/components/VScrollButton.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/VScrollButton.vue b/frontend/src/components/VScrollButton.vue index d87a3cc1942..47d7f5e1e47 100644 --- a/frontend/src/components/VScrollButton.vue +++ b/frontend/src/components/VScrollButton.vue @@ -1,7 +1,8 @@ From a3bc419984b38764954e4e4f8e434968d9740db0 Mon Sep 17 00:00:00 2001 From: Olga Bulat Date: Tue, 8 Oct 2024 16:10:23 +0300 Subject: [PATCH 11/12] Remove logging hot-fix --- frontend/nuxt.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts index fe05e65fc45..4e351645339 100644 --- a/frontend/nuxt.config.ts +++ b/frontend/nuxt.config.ts @@ -42,7 +42,7 @@ export default defineNuxtConfig({ }, plausible: { ignoredHostnames: ["localhost", "staging.openverse.org"], - logIgnoredEvents: false, + logIgnoredEvents: true, apiHost: "http://localhost:50290", domain: "localhost", }, From 8a84208e85685ae5d6dea3cc538e15f6a1bf4bbe Mon Sep 17 00:00:00 2001 From: Olga Bulat Date: Tue, 8 Oct 2024 16:49:12 +0300 Subject: [PATCH 12/12] Extract SearchParamsForEvent to simplify events --- frontend/src/components/VLoadMore.vue | 11 +----- frontend/src/components/VScrollButton.vue | 13 +------ frontend/src/stores/search.ts | 12 ++++++ frontend/src/types/analytics.ts | 46 ++++++++--------------- 4 files changed, 30 insertions(+), 52 deletions(-) diff --git a/frontend/src/components/VLoadMore.vue b/frontend/src/components/VLoadMore.vue index acf26cb71de..f733bc28b17 100644 --- a/frontend/src/components/VLoadMore.vue +++ b/frontend/src/components/VLoadMore.vue @@ -8,8 +8,6 @@ import { useElementVisibility } from "@vueuse/core" import { useMediaStore } from "~/stores/media" import { useSearchStore } from "~/stores/search" -import type { ResultKind } from "~/types/result" -import type { Collection } from "~/types/search" import type { SingleResultProps } from "~/types/collection-component-props" import type { SupportedSearchType } from "~/constants/media" @@ -36,16 +34,9 @@ const { $sendCustomEvent } = useNuxtApp() const { currentPage } = storeToRefs(mediaStore) const eventPayload = computed(() => { - let kind: ResultKind = - searchStore.strategy === "default" ? "search" : "collection" - const collectionType = (searchStore.collectionValue as Collection) ?? "null" return { - searchType: props.searchType, - query: props.searchTerm, + ...searchStore.searchParamsForEvent, resultPage: currentPage.value || 1, - kind, - collectionType, - collectionValue: searchStore.collectionValue ?? "null", } }) diff --git a/frontend/src/components/VScrollButton.vue b/frontend/src/components/VScrollButton.vue index 680eaf23e57..7bb1a89561c 100644 --- a/frontend/src/components/VScrollButton.vue +++ b/frontend/src/components/VScrollButton.vue @@ -6,9 +6,6 @@ import { computed } from "vue" import { useSearchStore } from "~/stores/search" import { useMediaStore } from "~/stores/media" -import type { ResultKind } from "~/types/result" -import type { Collection } from "~/types/search" - const positionWithoutSidebar = "ltr:right-4 rtl:left-4" const positionWithSidebar = "ltr:right-[22rem] rtl:left-[22rem]" @@ -54,19 +51,11 @@ const scrollToTop = () => { const element = isSearchRoute ? mainPage || window : window element.scrollTo({ top: 0, left: 0, behavior: "smooth" }) - const kind: ResultKind = - searchStore.strategy === "default" ? "search" : "collection" - const collectionType = (searchStore.collectionValue as Collection) ?? "null" - $sendCustomEvent("BACK_TO_TOP", { - searchType: mediaStore._searchType, - kind, - query: searchStore.searchTerm, + ...searchStore.searchParamsForEvent, resultPage: mediaStore.currentPage, scrollPixels: scrollPixels ?? -1, maxScroll: maxScroll ?? -1, - collectionType, - collectionValue: searchStore.collectionValue ?? "null", }) } diff --git a/frontend/src/stores/search.ts b/frontend/src/stores/search.ts index a4ae3dc721a..82438409bcc 100644 --- a/frontend/src/stores/search.ts +++ b/frontend/src/stores/search.ts @@ -46,6 +46,7 @@ import type { CollectionParams, PaginatedCollectionQuery, } from "~/types/search" +import { SearchParamsForEvent } from "~/types/analytics" import type { LocationQuery, RouteLocationNormalized } from "vue-router" @@ -240,6 +241,17 @@ export const useSearchStore = defineStore("search", { } } }, + searchParamsForEvent(): SearchParamsForEvent { + return { + kind: this.strategy === "default" ? "search" : "collection", + query: this.searchTerm, + searchType: isSearchTypeSupported(this.searchType) + ? this.searchType + : ALL_MEDIA, + collectionType: this.collectionParams?.collection ?? "null", + collectionValue: this.collectionValue ?? "null", + } + }, }, actions: { getApiRequestQuery(mediaType: SupportedMediaType) { diff --git a/frontend/src/types/analytics.ts b/frontend/src/types/analytics.ts index f7aa7242297..a5f64862d64 100644 --- a/frontend/src/types/analytics.ts +++ b/frontend/src/types/analytics.ts @@ -24,11 +24,17 @@ export type AudioComponent = | "VAllResultsGrid" /** - * Common properties related to searches - * on collection pages, added in the - * "Additional Search Views" project. + * Common properties for search and collection events. */ -type CollectionProperties = { +export type SearchParamsForEvent = { + kind: "search" | "collection" + searchType: SupportedSearchType + query: string + + /** + * Common properties related to searches on collection pages, added in the + * "Additional Search Views" project. + */ /** If a collection page, the type of collection */ collectionType: Collection | "null" /** A string representing a unique identifier for the collection */ @@ -106,19 +112,13 @@ export type Events = { * the search results page. */ BACK_TO_TOP: { - /** The media type being searched */ - searchType: SupportedSearchType - /** The kind of search reached (a collection, a standard search view, etc.) */ - kind: ResultKind - /** The search term */ - query: string /** The current page of results the user is on. */ resultPage: number /** The number of pixels the user has scrolled. */ scrollPixels: number - /** The maximum number of pixels the user has scrolled on this pageload. */ + /** The maximum number of pixels the user has scrolled on this page load. */ maxScroll: number - } & CollectionProperties + } & SearchParamsForEvent /** * Description: Whenever the user scrolls to the end of the results page. * Useful to evaluate how often users load more results or click @@ -132,15 +132,9 @@ export type Events = { * - Do users find a result before reaching the end of the results? */ REACH_RESULT_END: { - /** The media type being searched */ - searchType: SupportedSearchType - /** The kind of search reached (a collection, a standard search view, etc.) */ - kind: ResultKind - /** The search term */ - query: string /** The current page of results the user is on. */ resultPage: number - } & CollectionProperties + } & SearchParamsForEvent /** * Description: The user clicks the CTA button to the external source to use the image * Questions: @@ -287,7 +281,7 @@ export type Events = { * - How often do searches lead to clicking a result? * - Are there popular searches that do not result in result selection? */ - SELECT_SEARCH_RESULT: { + SELECT_SEARCH_RESULT: Omit & { /** The unique ID of the media */ id: string /** If the result is a related result, provide the ID of the 'original' result */ @@ -298,13 +292,11 @@ export type Events = { mediaType: SearchType /** The slug (not the prettified name) of the provider */ provider: string - /** The search term */ - query: string /** the reasons for why this result is considered sensitive */ sensitivities: string /** whether the result was blurred or visible when selected by the user */ isBlurred: boolean | "null" - } & CollectionProperties + } /** * Description: When a user opens the external sources popover. * Questions: @@ -330,16 +322,10 @@ export type Events = { * from anyway? */ LOAD_MORE_RESULTS: { - /** The media type being searched */ - searchType: SearchType - /** The kind of search (a collection, a standard search view, etc.) */ - kind: ResultKind - /** The search term */ - query: string /** The current page of results the user is on, * *before* loading more results.. */ resultPage: number - } & CollectionProperties + } & SearchParamsForEvent /** * Description: Whenever the user sets a filter. Filter category and key are the values used in code, not the user-facing filter labels. * Questions: