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

Analytics event: BACK_TO_SEARCH #1118

Merged
merged 8 commits into from May 2, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions frontend/src/components/VBackToSearchResultsLink.vue
Expand Up @@ -3,6 +3,7 @@
<VLink
class="time inline-flex flex-row items-center gap-2 rounded-sm p-2 pe-3 text-xs font-semibold text-dark-charcoal-70 hover:text-dark-charcoal"
v-bind="$attrs"
@mousedown="handleClick"
>
<VIcon name="chevron-left" :rtl-flip="true" />
{{ $t("single-result.back") }}
Expand All @@ -12,6 +13,9 @@
<script lang="ts">
import { defineComponent } from "vue"

import { useAnalytics } from "~/composables/use-analytics"
import { useSearchStore } from "~/stores/search"

import VIcon from "~/components/VIcon/VIcon.vue"
import VLink from "~/components/VLink.vue"

Expand All @@ -25,5 +29,29 @@ export default defineComponent({
VLink,
},
inheritAttrs: false,
props: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These must be added below the inheritAttrs field to match the recommended option order.

Also, if you add new required props to a component, you will need to update the usage of that component. This particular component VBackToSearchResultsLink.vue is used in the following locations, which need to be updated:

  • frontend/src/pages/audio/_id/index.vue
  • frontend/src/pages/image/_id/index.vue
  • frontend/src/components/meta/VBackToSearchResultsLink.stories.mdx

/**
* The unique ID of the media
*/
id: {
type: String,
required: true,
},
},
setup(props) {
const { sendCustomEvent } = useAnalytics()
const searchStore = useSearchStore()

const handleClick = () => {
sendCustomEvent("BACK_TO_SEARCH", {
id: props.id,
searchType: searchStore.searchType,
})
}

return {
handleClick,
}
},
})
</script>
1 change: 1 addition & 0 deletions frontend/src/components/VLink.vue
Expand Up @@ -5,6 +5,7 @@
:class="{ 'inline-flex flex-row items-center gap-2': showExternalIcon }"
:to="linkTo"
v-on="$listeners"
@mousedown.native="$emit('mousedown', $event)"
@click.native="$emit('click', $event)"
>
<slot />
Expand Down
Expand Up @@ -8,7 +8,7 @@ import VBackToSearchResultsLink from "~/components/VBackToSearchResultsLink.vue"
/>

export const Template = (args) => ({
template: `<VBackToSearchResultsLink v-bind="args"/>`,
template: `<VBackToSearchResultsLink v-bind="args" :id="id" />`,
components: { VBackToSearchResultsLink },
setup() {
return { args }
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/audio/_id/index.vue
@@ -1,7 +1,7 @@
<template>
<VSkipToContentContainer as="main">
<div v-if="backToSearchPath" class="w-full px-2 py-2 md:px-6">
<VBackToSearchResultsLink :href="backToSearchPath" />
<VBackToSearchResultsLink :id="audio.id" :href="backToSearchPath" />
</div>

<VAudioTrack layout="full" :audio="audio" class="main-track" />
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/image/_id/index.vue
@@ -1,7 +1,7 @@
<template>
<VSkipToContentContainer as="main">
<div v-if="backToSearchPath" class="w-full px-2 py-2 md:px-6">
<VBackToSearchResultsLink :href="backToSearchPath" />
<VBackToSearchResultsLink :id="image.id" :href="backToSearchPath" />
</div>

<figure class="relative mb-4 border-b border-dark-charcoal-20 px-6">
Expand Down
13 changes: 12 additions & 1 deletion frontend/src/types/analytics.ts
@@ -1,4 +1,4 @@
import type { MediaType } from "~/constants/media"
import type { MediaType, SearchType } from "~/constants/media"
import type { ReportReason } from "~/constants/content-report"

/**
Expand Down Expand Up @@ -26,6 +26,17 @@ export type Events = {
/** the identifier of the image */
identifier: string
}
/**
* Click on the 'back to search' link on a single result
*
* - Are these links used much? Are they necessary?
*/
BACK_TO_SEARCH: {
/** The unique ID of the media */
id: string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small grammar nit.

Suggested change
/** The unique ID of the media */
/** the unique ID of the media */

/** The content type being searched (can include All content) */
searchType: SearchType
}
/**
* Description: The user clicks the CTA button to the external source to use the image
* Questions:
Expand Down
4 changes: 4 additions & 0 deletions frontend/test/playwright/e2e/attribution.spec.ts
@@ -1,5 +1,7 @@
import { test, expect } from "@playwright/test"

import { turnOnAnalytics } from "~~/test/playwright/utils/navigation"

test.describe.configure({ mode: "parallel" })

test.beforeEach(async ({ context }) => {
Expand Down Expand Up @@ -63,6 +65,8 @@ test("sends analytics event on copy", async ({ page }) => {
const id = "e9d97a98-621b-4ec2-bf70-f47a74380452"
const format = "rich"

await turnOnAnalytics(page)

await page.goto(`${mediaType}/${id}?ff_analytics=on`)
await page.click(`[id="copyattr-${format}"]`)

Expand Down
5 changes: 5 additions & 0 deletions frontend/test/playwright/utils/navigation.ts
Expand Up @@ -404,3 +404,8 @@ export const closeFiltersUsingCookies = async (page: Page) => {
export const setBreakpointCookie = async (page: Page, breakpoint: string) => {
await setCookies(page.context(), { uiBreakpoint: breakpoint })
}

export const turnOnAnalytics = async (page: Page) => {
await page.goto("/preferences")
await page.getByLabel("Record custom events and page views.").click()
}
8 changes: 7 additions & 1 deletion frontend/test/unit/setup.js
Expand Up @@ -19,7 +19,13 @@ Vue.use(VueI18n)
*/
config.stubs["nuxt-link"] = Vue.component("NuxtLink", {
props: ["to"],
template: '<a :href="to" v-on="$listeners"><slot /></a>',
methods: {
handleClick() {
this.$emit("mousedown")
this.$emit("click", new MouseEvent("click"))
},
},
template: '<a :href="to" v-on="$listeners" @click="handleClick"><slot /></a>',
obulat marked this conversation as resolved.
Show resolved Hide resolved
})

config.stubs["svg-icon"] = Vue.component("SvgIcon", {
Expand Down
@@ -0,0 +1,45 @@
import { createLocalVue } from "@vue/test-utils"
import { fireEvent, render } from "@testing-library/vue"

import { PiniaVuePlugin, createPinia } from "~~/test/unit/test-utils/pinia"

import { useAnalytics } from "~/composables/use-analytics"

import VBackToSearchResultsLink from "~/components/VBackToSearchResultsLink.vue"

jest.mock("~/composables/use-analytics", () => ({
useAnalytics: jest.fn(() => ({
sendCustomEvent: jest.fn(),
})),
}))

const localVue = createLocalVue()
localVue.use(PiniaVuePlugin)
describe("VBackToSearchResultsLink", () => {
it("should send analytics event when clicked", async () => {
const sendCustomEventMock = jest.fn()

useAnalytics.mockImplementation(() => ({
sendCustomEvent: sendCustomEventMock,
}))
obulat marked this conversation as resolved.
Show resolved Hide resolved

const propsData = {
id: "123",
href: "/search",
}

const screen = render(VBackToSearchResultsLink, {
localVue,
pinia: createPinia(),
propsData,
})
const link = screen.getByText("single-result.back")

await fireEvent.click(link)

expect(sendCustomEventMock).toHaveBeenCalledWith("BACK_TO_SEARCH", {
id: propsData.id,
searchType: "all",
})
})
})