diff --git a/frontend/shared/types/analytics.ts b/frontend/shared/types/analytics.ts index bf4340938b1..306a01029a4 100644 --- a/frontend/shared/types/analytics.ts +++ b/frontend/shared/types/analytics.ts @@ -43,6 +43,10 @@ export type SearchParamsForEvent = { export type SearchResultParams = { /** The unique ID of the media */ id: string + /** The search term */ + query: string + /** The position, not index, of the result in the search results */ + position: number } /** @@ -78,11 +82,9 @@ export type Events = { * - How often are searches returning fewer than one page of results? * - How many results do most searches yield? */ - GET_SEARCH_RESULTS: { + GET_SEARCH_RESULTS: SearchParamsForEvent & { /** the media type of the results. Is different from `searchType` when searchType is "all media" */ mediaType: SupportedMediaType - /** The search term */ - query: string /** The number of results found for this search */ resultsCount: number } @@ -293,6 +295,7 @@ export type Events = { * - Which results are most popular for given searches? * - How often do searches lead to clicking a result? * - Are there popular searches that do not result in result selection? + * - How often do users select results from the "All content" search? */ SELECT_SEARCH_RESULT: SearchResultParams & Pick & { diff --git a/frontend/shared/types/collection-component-props.ts b/frontend/shared/types/collection-component-props.ts index 1811393e894..130ff0eeb95 100644 --- a/frontend/shared/types/collection-component-props.ts +++ b/frontend/shared/types/collection-component-props.ts @@ -6,6 +6,7 @@ export type SingleResultProps = { kind: ResultKind searchTerm: string relatedTo?: string + position?: number } export type CommonCollectionProps = SingleResultProps & { collectionLabel: string diff --git a/frontend/shared/utils/query-utils.ts b/frontend/shared/utils/query-utils.ts index 931cf0ceffa..3959ea391c0 100644 --- a/frontend/shared/utils/query-utils.ts +++ b/frontend/shared/utils/query-utils.ts @@ -11,6 +11,19 @@ export const firstParam = ( return params ?? null } +export function getNumber( + params: LocationQueryValue | LocationQueryValue[] | undefined +): number { + const value = firstParam(params) + return value ? parseInt(value, 10) : -1 +} + +export function getString( + params: LocationQueryValue | LocationQueryValue[] | undefined +): string { + return firstParam(params) ?? "" +} + export const validateUUID = (id: string | undefined | null) => { if (!id) { return false diff --git a/frontend/src/components/VImageResult/VImageResult.vue b/frontend/src/components/VImageResult/VImageResult.vue index cc3bb32123e..c5c9c89c143 100644 --- a/frontend/src/components/VImageResult/VImageResult.vue +++ b/frontend/src/components/VImageResult/VImageResult.vue @@ -25,12 +25,14 @@ const props = withDefaults( * uses the image's intrinsic size. */ aspectRatio?: AspectRatio + position?: number } >(), { aspectRatio: "square", kind: "search", relatedTo: "null", + position: -1, } ) @@ -61,7 +63,7 @@ const imageUrl = computed(() => { }) const imageLink = computed(() => { - return `/image/${props.image.id}/${singleResultQuery(props.searchTerm)}` + return `/image/${props.image.id}/${singleResultQuery(props.searchTerm, props.position)}` }) /** @@ -110,14 +112,13 @@ const sendSelectSearchResultEvent = (event: MouseEvent) => { return } - const { searchType, collectionType } = searchStore.searchParamsForEvent $sendCustomEvent("SELECT_SEARCH_RESULT", { - searchType, - collectionType, + ...searchStore.searchParamsForEvent, id: props.image.id, kind: props.kind, mediaType: IMAGE, provider: props.image.provider, + position: props.position, relatedTo: props.relatedTo ?? "null", sensitivities: props.image.sensitivity?.join(",") ?? "", isBlurred: shouldBlur.value ?? "null", diff --git a/frontend/src/components/VMediaInfo/VGetMediaButton.vue b/frontend/src/components/VMediaInfo/VGetMediaButton.vue index d81cafd1494..217788ade07 100644 --- a/frontend/src/components/VMediaInfo/VGetMediaButton.vue +++ b/frontend/src/components/VMediaInfo/VGetMediaButton.vue @@ -3,6 +3,7 @@ import { useNuxtApp } from "#imports" import type { SupportedMediaType } from "#shared/constants/media" import type { Media } from "#shared/types/media" +import { useRouteResultParams } from "~/composables/use-route-result-params" import VButton from "~/components/VButton.vue" @@ -10,12 +11,12 @@ const props = defineProps<{ media: Media mediaType: SupportedMediaType }>() - +const { resultParams } = useRouteResultParams() const { $sendCustomEvent } = useNuxtApp() const sendGetMediaEvent = () => { $sendCustomEvent("GET_MEDIA", { - id: props.media.id, + ...resultParams.value, provider: props.media.provider, mediaType: props.mediaType, }) diff --git a/frontend/src/components/VMediaInfo/VLicenseTabPanel.vue b/frontend/src/components/VMediaInfo/VLicenseTabPanel.vue index 1ecc8b4d7b1..8079e49c41d 100644 --- a/frontend/src/components/VMediaInfo/VLicenseTabPanel.vue +++ b/frontend/src/components/VMediaInfo/VLicenseTabPanel.vue @@ -2,6 +2,7 @@ import { useNuxtApp } from "#imports" import type { SupportedMediaType } from "#shared/constants/media" +import { useRouteResultParams } from "~/composables/use-route-result-params" import VCopyButton from "~/components/VCopyButton.vue" import VTabPanel from "~/components/VTabs/VTabPanel.vue" @@ -23,10 +24,12 @@ const props = defineProps<{ mediaType: SupportedMediaType }>() +const { resultParams } = useRouteResultParams() + const { $sendCustomEvent } = useNuxtApp() const handleCopy = () => { $sendCustomEvent("COPY_ATTRIBUTION", { - id: props.mediaId, + ...resultParams.value, format: props.tab, mediaType: props.mediaType, }) diff --git a/frontend/src/components/VSearchResultsGrid/VAllResultsGrid.vue b/frontend/src/components/VSearchResultsGrid/VAllResultsGrid.vue index 932f28077c6..775c51f88b8 100644 --- a/frontend/src/components/VSearchResultsGrid/VAllResultsGrid.vue +++ b/frontend/src/components/VSearchResultsGrid/VAllResultsGrid.vue @@ -38,11 +38,12 @@ const isSm = computed(() => uiStore.isBreakpoint("sm")) " :aria-label="collectionLabel" > -