diff --git a/src/background/telemetry.test.ts b/src/background/telemetry.test.ts index db43b94de7..771afb6008 100644 --- a/src/background/telemetry.test.ts +++ b/src/background/telemetry.test.ts @@ -21,7 +21,7 @@ import { TEST_flushAll, } from "@/background/telemetry"; import { appApiMock } from "@/testUtils/appApiMock"; -import { type Event } from "@/telemetry/events"; +import { type TelemetryEvent } from "@/telemetry/telemetryTypes"; const EXPECTED_RUNTIME_ID = "abc123"; const expectedManifestValues = { @@ -53,13 +53,13 @@ beforeEach(async () => { describe("recordEvent", () => { test("runs", async () => { - await recordEvent({ event: "TestEvent" as Event, data: {} }); + await recordEvent({ event: "TestEvent" as TelemetryEvent, data: {} }); const events = await flushEvents(); expect(events).toHaveLength(1); }); test("includes expected default properties", async () => { - const testEvent = { event: "TestEvent" as Event, data: {} }; + const testEvent = { event: "TestEvent" as TelemetryEvent, data: {} }; await recordEvent(testEvent); const events = await flushEvents(); expect(events[0]).toMatchObject({ @@ -76,7 +76,7 @@ describe("recordEvent", () => { test("successfully persists concurrent telemetry events to local storage", async () => { // Easiest way to test race condition without having to mock const recordTestEvents = Array.from({ length: 100 }, async () => - recordEvent({ event: "TestEvent" as Event, data: {} }), + recordEvent({ event: "TestEvent" as TelemetryEvent, data: {} }), ); await Promise.all(recordTestEvents); diff --git a/src/background/telemetry.ts b/src/background/telemetry.ts index 797cee2082..4e1018dac4 100644 --- a/src/background/telemetry.ts +++ b/src/background/telemetry.ts @@ -30,7 +30,7 @@ import { count as logSize } from "@/telemetry/logging"; import { count as traceSize } from "@/telemetry/trace"; import { getUUID } from "@/telemetry/telemetryHelpers"; import { getExtensionVersion, getTabsWithAccess } from "@/utils/extensionUtils"; -import { type Event } from "@/telemetry/events"; +import { type TelemetryEvent } from "@/telemetry/telemetryTypes"; const EVENT_BUFFER_DEBOUNCE_MS = 2000; const EVENT_BUFFER_MAX_MS = 10_000; @@ -333,7 +333,7 @@ export async function recordEvent({ event, data = {}, }: { - event: Event; + event: TelemetryEvent; data: UnknownObject | undefined; }): Promise { if (await allowsTrack()) { diff --git a/src/hooks/useUserAction.ts b/src/hooks/useUserAction.ts index 7636474575..668e5f1b35 100644 --- a/src/hooks/useUserAction.ts +++ b/src/hooks/useUserAction.ts @@ -17,12 +17,12 @@ import { type DependencyList, useCallback } from "react"; import notify from "@/utils/notify"; -import { type Event } from "@/telemetry/events"; +import { type TelemetryEvent } from "@/telemetry/telemetryTypes"; import { CancelError } from "@/errors/businessErrors"; import reportEvent from "@/telemetry/reportEvent"; type Options = { - event?: Event; + event?: TelemetryEvent; errorMessage?: string; successMessage?: string; }; diff --git a/src/telemetry/events.ts b/src/telemetry/events.ts index b707750773..15bd4ab1cb 100644 --- a/src/telemetry/events.ts +++ b/src/telemetry/events.ts @@ -166,26 +166,3 @@ export const Events = { SHORTCUT_SNIPPET_RUN: "TextCommandRun", } as const; - -export type Event = (typeof Events)[keyof typeof Events]; - -const RESERVED_KEYS = [ - "blockId", - "blockVersion", - "blueprintId", - "blueprintVersion", - "extensionId", - "extensionLabel", - "extensionPointId", - "extensions", - "recipeId", - "recipeToActivate", - "serviceId", - "serviceVersion", -] as const; - -type ReservedKeys = { - [K in (typeof RESERVED_KEYS)[number]]?: never; -}; - -export type ReportEventData = UnknownObject & ReservedKeys; diff --git a/src/telemetry/initErrorReporter.ts b/src/telemetry/initErrorReporter.ts index 47e1ffb7ac..047ccc7867 100644 --- a/src/telemetry/initErrorReporter.ts +++ b/src/telemetry/initErrorReporter.ts @@ -25,8 +25,8 @@ import type { LogsEvent } from "@datadog/browser-logs/src/logsEvent.types"; import { cleanDatadogVersionName, mapAppUserToTelemetryUser, - type TelemetryUser, } from "@/telemetry/telemetryHelpers"; +import { type TelemetryUser } from "@/telemetry/telemetryTypes"; // eslint-disable-next-line prefer-destructuring -- process.env const ENVIRONMENT = process.env.ENVIRONMENT; diff --git a/src/telemetry/reportEvent.ts b/src/telemetry/reportEvent.ts index 5ef66d7c2d..632928c98b 100644 --- a/src/telemetry/reportEvent.ts +++ b/src/telemetry/reportEvent.ts @@ -16,8 +16,12 @@ */ import { backgroundTarget as bg, getNotifier } from "webext-messenger"; -import { type Event, type ReportEventData } from "@/telemetry/events"; import { expectContext } from "@/utils/expectContext"; +import { + type TelemetryEvent, + type ReportEventData, +} from "@/telemetry/telemetryTypes"; +import { mapEventDataToDeprecatedTerminology } from "@/telemetry/telemetryHelpers"; expectContext( "extension", @@ -27,64 +31,15 @@ expectContext( // Private method. Do not move to api.ts const _record = getNotifier("RECORD_EVENT", bg); -function transformEventData(data: UnknownObject): UnknownObject { - if (data.brickId) { - data.blockId = data.brickId; - } - - if (data.brickVersion) { - data.blockVersion = data.brickVersion; - } - - if (data.integrationId) { - data.serviceId = data.integrationId; - } - - if (data.integrationVersion) { - data.serviceVersion = data.integrationVersion; - } - - if (data.modId) { - data.blueprintId = data.modId; - data.recipeId = data.modId; - } - - if (data.modComponentId) { - data.extensionId = data.modComponentId; - } - - if (data.modComponentLabel) { - data.extensionLabel = data.modComponentLabel; - } - - if (data.modComponents) { - data.extensions = data.modComponents; - } - - if (data.modToActivate) { - data.recipeToActivate = data.modToActivate; - } - - if (data.modVersion) { - data.blueprintVersion = data.modVersion; - } - - if (data.starterBrickId) { - data.extensionPointId = data.starterBrickId; - } - - return data; -} - /** * Report an event to the PixieBrix telemetry service, if the user doesn't have DNT set. * @see selectEventData */ export default function reportEvent( - event: Event, + event: TelemetryEvent, data: ReportEventData = {}, ): void { // eslint-disable-next-line prefer-rest-params -- Needs `arguments` to avoid printing the default console.debug(...arguments); - _record({ event, data: transformEventData(data) }); + _record({ event, data: mapEventDataToDeprecatedTerminology(data) }); } diff --git a/src/telemetry/telemetryHelpers.ts b/src/telemetry/telemetryHelpers.ts index dca63053a8..11113af186 100644 --- a/src/telemetry/telemetryHelpers.ts +++ b/src/telemetry/telemetryHelpers.ts @@ -16,23 +16,12 @@ */ import type { UserData } from "@/auth/authTypes"; +import { type TelemetryUser } from "@/telemetry/telemetryTypes"; import { uuidv4 } from "@/types/helpers"; import type { UUID } from "@/types/stringTypes"; import { once } from "lodash"; import { StorageItem } from "webext-storage"; -/** - * The Person model for application error telemetry. - */ -export type TelemetryUser = { - /** - * User id or browser distinct id, if the user is anonymous. - */ - id: UUID; - email?: string; - organizationId?: UUID | null; -}; - export const uuidStorage = new StorageItem("USER_UUID"); /** @@ -97,3 +86,54 @@ export async function mapAppUserToTelemetryUser( organizationId: telemetryOrganizationId ?? organizationId, }; } + +export function mapEventDataToDeprecatedTerminology( + data: UnknownObject, +): UnknownObject { + if (data.brickId) { + data.blockId = data.brickId; + } + + if (data.brickVersion) { + data.blockVersion = data.brickVersion; + } + + if (data.integrationId) { + data.serviceId = data.integrationId; + } + + if (data.integrationVersion) { + data.serviceVersion = data.integrationVersion; + } + + if (data.modId) { + data.blueprintId = data.modId; + data.recipeId = data.modId; + } + + if (data.modComponentId) { + data.extensionId = data.modComponentId; + } + + if (data.modComponentLabel) { + data.extensionLabel = data.modComponentLabel; + } + + if (data.modComponents) { + data.extensions = data.modComponents; + } + + if (data.modToActivate) { + data.recipeToActivate = data.modToActivate; + } + + if (data.modVersion) { + data.blueprintVersion = data.modVersion; + } + + if (data.starterBrickId) { + data.extensionPointId = data.starterBrickId; + } + + return data; +} diff --git a/src/telemetry/telemetryTypes.ts b/src/telemetry/telemetryTypes.ts new file mode 100644 index 0000000000..673463fd63 --- /dev/null +++ b/src/telemetry/telemetryTypes.ts @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 PixieBrix, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { type Events } from "@/telemetry/events"; +import { type UUID } from "@/types/stringTypes"; + +/** + * The Person model for application error telemetry. + */ +export type TelemetryUser = { + /** + * User id or browser distinct id, if the user is anonymous. + */ + id: UUID; + email?: string; + organizationId?: UUID | null; +}; + +export type TelemetryEvent = (typeof Events)[keyof typeof Events]; + +const RESERVED_KEYS = [ + "blockId", + "blockVersion", + "blueprintId", + "blueprintVersion", + "extensionId", + "extensionLabel", + "extensionPointId", + "extensions", + "recipeId", + "recipeToActivate", + "serviceId", + "serviceVersion", +] as const; + +type ReservedKeys = { + [K in (typeof RESERVED_KEYS)[number]]?: never; +}; + +export type ReportEventData = UnknownObject & ReservedKeys; diff --git a/src/tinyPages/offscreen.ts b/src/tinyPages/offscreen.ts index f5e4c57243..581c587d3f 100644 --- a/src/tinyPages/offscreen.ts +++ b/src/tinyPages/offscreen.ts @@ -16,7 +16,7 @@ */ import type { MessageContext } from "@/types/loggerTypes"; -import { type TelemetryUser } from "@/telemetry/telemetryHelpers"; +import { type TelemetryUser } from "@/telemetry/telemetryTypes"; import { type SemVerString } from "@/types/registryTypes"; import { type SerializedError } from "@/types/messengerTypes"; import { deserializeError } from "serialize-error";