diff --git a/frontend/src/data/media-service.ts b/frontend/src/data/media-service.ts index 214d3fb8568..e377e15233a 100644 --- a/frontend/src/data/media-service.ts +++ b/frontend/src/data/media-service.ts @@ -6,8 +6,6 @@ import type { import type { ApiService } from "~/data/api-service" import type { DetailFromMediaType, Media } from "~/types/media" import { AUDIO, type SupportedMediaType } from "~/constants/media" -import { useAnalytics } from "~/composables/use-analytics" -import type { EventName } from "~/types/analytics" import type { AxiosResponse } from "axios" @@ -24,52 +22,10 @@ export interface MediaResult< class MediaService { private readonly apiService: ApiService private readonly mediaType: T["frontendMediaType"] - private readonly searchEvent: EventName constructor(apiService: ApiService, mediaType: T["frontendMediaType"]) { this.apiService = apiService this.mediaType = mediaType - this.searchEvent = - `${this.mediaType.toUpperCase()}_SEARCH_RESPONSE_TIME` as EventName - } - - /** - * Processes AxiosResponse from a search query to - * construct SEARCH_RESPONSE_TIME analytics event. - * @param response - Axios response - * @param requestDatetime - datetime before request was sent - */ - recordSearchTime(response: AxiosResponse, requestDatetime: Date) { - const REQUIRED_HEADERS = ["date", "cf-cache-status", "cf-ray"] - - const responseHeaders = response.headers - if (!REQUIRED_HEADERS.every((header) => header in responseHeaders)) { - return - } - - const responseDatetime = new Date(responseHeaders["date"]) - if (responseDatetime < requestDatetime) { - // response returned was from the local cache - return - } - - const cfRayIATA = responseHeaders["cf-ray"].split("-")[1] - if (cfRayIATA === undefined) { - return - } - - const elapsedSeconds = Math.floor( - (responseDatetime.getTime() - requestDatetime.getTime()) / 1000 - ) - const url = new URL(response.request?.responseURL) - - const { sendCustomEvent } = useAnalytics() - sendCustomEvent(this.searchEvent, { - cfCacheStatus: responseHeaders["cf-cache-status"], - cfRayIATA: cfRayIATA, - elapsedTime: elapsedSeconds, - queryString: url.search, - }) } /** @@ -103,16 +59,11 @@ class MediaService { params.peaks = "true" } - const requestDatetime = new Date() - const res = await this.apiService.query>( this.mediaType, slug, params as unknown as Record ) - - this.recordSearchTime(res, requestDatetime) - return this.transformResults(res.data) } diff --git a/frontend/src/types/analytics.ts b/frontend/src/types/analytics.ts index ada52aa1424..f37f81c7026 100644 --- a/frontend/src/types/analytics.ts +++ b/frontend/src/types/analytics.ts @@ -412,34 +412,6 @@ export type Events = { /** the reasons for why this result is considered sensitive */ sensitivities: string } - - /** - * Description: Time client-side search responses. Gives us observability into - * real user experience of search timings. - * Questions: - * - How long does it take for the client to receive a response to search requests? - */ - IMAGE_SEARCH_RESPONSE_TIME: { - /** the Cloudflare cache status, denoting whether the request hit Cloudflare or went all the way to our servers */ - cfCacheStatus: string - /** the IATA location identifier as part of the `cf-ray` header, indicating the data centre the request passed through */ - cfRayIATA: string - /** how many seconds it took to receive a response for the request */ - elapsedTime: number - /** full query string */ - queryString: string - } - - AUDIO_SEARCH_RESPONSE_TIME: { - /** the Cloudflare cache status, denoting whether the request hit Cloudflare or went all the way to our servers */ - cfCacheStatus: string - /** the IATA location identifier as part of the `cf-ray` header, indicating the data centre the request passed through */ - cfRayIATA: string - /** how many seconds it took to receive a response for the request */ - elapsedTime: number - /** full query string */ - queryString: string - } } /** diff --git a/frontend/test/unit/specs/data/media-service.spec.ts b/frontend/test/unit/specs/data/media-service.spec.ts deleted file mode 100644 index 73aea1581a5..00000000000 --- a/frontend/test/unit/specs/data/media-service.spec.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { mockCreateApiService } from "~~/test/unit/test-utils/api-service-mock" - -import { initServices } from "~/stores/media/services" -import { useAnalytics } from "~/composables/use-analytics" - -const API_IMAGES_ENDPOINT = "images/" -const API_AUDIO_ENDPOINT = "audio/" -const BASE_URL = "https://www.mockapiservice.openverse.engineering/v1/" - -jest.mock("~/composables/use-analytics") - -const sendCustomEventMock = jest.fn() -const mockedUseAnalytics = useAnalytics as jest.Mock< - ReturnType -> -mockedUseAnalytics.mockImplementation(() => ({ - sendCustomEvent: sendCustomEventMock, -})) - -beforeAll(() => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.useFakeTimers("modern") - jest.setSystemTime(new Date("Tue, 17 Dec 2019 20:20:00 GMT")) -}) - -afterAll(() => { - jest.useRealTimers() -}) - -describe("Media Service search and recordSearchTime", () => { - beforeEach(() => { - sendCustomEventMock.mockClear() - }) - - it("should not send a SEARCH_RESPONSE_TIME analytics event if any required header is missing", async () => { - mockCreateApiService((axiosMockAdapter) => { - axiosMockAdapter.onGet().reply(200, {}) - }) - - await initServices.image().search({}) - - expect(sendCustomEventMock).not.toHaveBeenCalled() - }) - - it("should not send a SEARCH_RESPONSE_TIME analytics event if the response was locally cached", async () => { - mockCreateApiService((axiosMockAdapter) => { - axiosMockAdapter.onGet().reply(() => { - return [ - 200, - {}, - { - date: "Tue, 17 Dec 2019 19:00:00 GMT", - "cf-ray": "230b030023ae284c-SJC", - "cf-cache-status": "HIT", - }, - ] - }) - }) - - await initServices.audio().search({}) - - expect(sendCustomEventMock).not.toHaveBeenCalled() - }) - - it("should not send a SEARCH_RESPONSE_TIME analytics event if the cf-ray is malformed", async () => { - mockCreateApiService((axiosMockAdapter) => { - axiosMockAdapter.onGet().reply((config) => { - // force config.url so the responseURL is set in the AxiosRequest - config.url = BASE_URL + config.url - return [ - 200, - {}, - { - date: "Tue, 17 Dec 2019 20:30:00 GMT", - "cf-ray": "230b030023ae284c", - "cf-cache-status": "HIT", - }, - ] - }) - }) - - await initServices.audio().search({}) - - expect(sendCustomEventMock).not.toHaveBeenCalled() - }) - - it("should send SEARCH_RESPONSE_TIME analytics with correct parameters", async () => { - mockCreateApiService((axiosMockAdapter) => { - axiosMockAdapter - .onGet(API_IMAGES_ENDPOINT, { params: { q: "apple" } }) - .reply((config) => { - config.url = BASE_URL + config.url + "?q=apple" - return [ - 200, - {}, - { - date: "Tue, 17 Dec 2019 20:20:02 GMT", - "cf-ray": "230b030023ae2822-SJC", - "cf-cache-status": "HIT", - }, - ] - }) - - axiosMockAdapter - .onGet(API_AUDIO_ENDPOINT, { params: { q: "table", peaks: "true" } }) - .reply((config) => { - config.url = BASE_URL + config.url + "?q=table&peaks=true" - return [ - 200, - {}, - { - date: "Tue, 17 Dec 2019 20:20:03 GMT", - "cf-ray": "240b030b23ae2822-LHR", - "cf-cache-status": "MISS", - }, - ] - }) - }) - - const IMAGE_QUERY_PARAMS = { q: "apple" } - await initServices.image().search(IMAGE_QUERY_PARAMS) - - expect(sendCustomEventMock).toHaveBeenCalledWith( - "IMAGE_SEARCH_RESPONSE_TIME", - { - cfCacheStatus: "HIT", - cfRayIATA: "SJC", - elapsedTime: 2, - queryString: "?q=apple", - } - ) - - const AUDIO_QUERY_PARAMS = { q: "table" } - await initServices.audio().search(AUDIO_QUERY_PARAMS) - - expect(sendCustomEventMock).toHaveBeenCalledWith( - "AUDIO_SEARCH_RESPONSE_TIME", - { - cfCacheStatus: "MISS", - cfRayIATA: "LHR", - elapsedTime: 3, - queryString: "?q=table&peaks=true", - } - ) - }) -})