From 30c390c19c6a15f3251132447c3ce5e98928a288 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Wed, 22 Jan 2025 23:52:32 +0800 Subject: [PATCH] chore: unify telemetry event sending (#32861) * chore: unify telemetry event sending * organize some imports * remove final useTelemetryProps * add www telemetry actions typing and docs * remove gaevents * fix build * move telemetry identify into PageTelemetry * fix telemetry constants import --------- Co-authored-by: Pamela Chia Co-authored-by: Joshen Lim --- .github/CODEOWNERS | 2 +- apps/docs/components/Feedback/Feedback.tsx | 6 +- apps/docs/lib/telemetry.ts | 63 ++---- .../FeaturePreview/FeaturePreviewModal.tsx | 2 +- .../Connect/DatabaseConnectionString.tsx | 2 +- .../CronJobs/CreateCronJobSheet.tsx | 2 +- .../Integrations/CronJobs/CronJobCard.tsx | 2 +- .../Integrations/CronJobs/CronJobsTab.tsx | 2 +- .../Integrations/CronJobs/DeleteCronJob.tsx | 2 +- .../ProjectCreation/SchemaGenerator.tsx | 2 +- .../Inspector/ChooseChannelPopover/index.tsx | 10 +- .../interfaces/Realtime/Inspector/Header.tsx | 4 +- .../Realtime/Inspector/MessageSelection.tsx | 6 +- .../Realtime/Inspector/MessagesTable.tsx | 6 +- .../Inspector/RealtimeFilterPopover/index.tsx | 6 +- .../Inspector/RealtimeTokensPopover/index.tsx | 2 +- .../interfaces/Realtime/Inspector/index.tsx | 2 +- .../interfaces/SQLEditor/SQLEditor.tsx | 2 +- .../SQLEditor/SQLTemplates/SQLQuickstarts.tsx | 2 +- .../SQLEditor/SQLTemplates/SQLTemplates.tsx | 4 +- .../UtilityPanel/ResultsDropdown.tsx | 6 +- .../interfaces/SignIn/SignInForm.tsx | 2 +- .../interfaces/SignIn/SignInSSOForm.tsx | 2 +- .../ui/AIAssistantPanel/AIAssistant.tsx | 2 +- .../ui/AIAssistantPanel/MessageMarkdown.tsx | 2 +- .../ui/QueryBlock/EditQueryButton.tsx | 2 +- .../data/telemetry/send-event-mutation.ts | 137 ++----------- .../data/telemetry/send-identify-mutation.ts | 70 ------- apps/studio/lib/profile.tsx | 9 +- apps/studio/pages/new/v2/[slug].tsx | 2 +- apps/studio/pages/sign-in-mfa.tsx | 2 +- apps/www/components/Hero/Hero.tsx | 27 +-- apps/www/components/Hero/HeroFrameworks.tsx | 42 ++-- .../Pricing/PricingComparisonTable.tsx | 68 ++++--- apps/www/components/Pricing/PricingPlans.tsx | 24 +-- apps/www/components/Products/index.tsx | 59 +----- apps/www/data/frameworks.ts | 7 - apps/www/lib/gaEvents.ts | 95 --------- apps/www/lib/telemetry.ts | 62 ++---- apps/www/pages/events/[slug].tsx | 42 ++-- packages/common/hooks/index.ts | 1 - packages/common/hooks/useTelemetryCookie.tsx | 32 +--- packages/common/hooks/useTelemetryProps.ts | 31 --- .../common/telemetry-constants.ts | 180 ++++++++++++++++++ packages/common/telemetry.tsx | 111 +++++++++-- packages/ui-patterns/ConsentToast/index.tsx | 33 +--- 46 files changed, 481 insertions(+), 698 deletions(-) delete mode 100644 apps/studio/data/telemetry/send-identify-mutation.ts delete mode 100644 apps/www/lib/gaEvents.ts delete mode 100644 packages/common/hooks/useTelemetryProps.ts rename apps/studio/lib/constants/telemetry.ts => packages/common/telemetry-constants.ts (76%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 92882cec376..b1831720cca 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,4 +18,4 @@ /apps/studio/components/interfaces/Organization/Documents/ @supabase/security /apps/studio/pages/new/index.tsx @supabase/security -/apps/studio/lib/constants/telemetry.ts @4L3k51 @loong @pamelachia \ No newline at end of file +/apps/studio/common/telemetry-constants.ts @4L3k51 @loong @pamelachia \ No newline at end of file diff --git a/apps/docs/components/Feedback/Feedback.tsx b/apps/docs/components/Feedback/Feedback.tsx index b6928f81971..f16063a9209 100644 --- a/apps/docs/components/Feedback/Feedback.tsx +++ b/apps/docs/components/Feedback/Feedback.tsx @@ -13,6 +13,7 @@ import { } from 'react' import { type Database, useConstant, useIsLoggedIn } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { Button, cn } from 'ui' import { IS_PLATFORM } from '~/lib/constants' @@ -108,9 +109,8 @@ function Feedback({ className }: { className?: string }) { function handleVote(response: Response) { sendTelemetryEvent({ - category: 'docs', - action: 'feedback_voted', - label: response, + action: TelemetryActions.DOCS_FEEDBACK_CLICKED, + properties: { response }, }) sendFeedbackVote(response) dispatch({ event: 'VOTED', response }) diff --git a/apps/docs/lib/telemetry.ts b/apps/docs/lib/telemetry.ts index 53dec9fff2c..e9dd341af3a 100644 --- a/apps/docs/lib/telemetry.ts +++ b/apps/docs/lib/telemetry.ts @@ -1,57 +1,16 @@ -import { isBrowser } from 'common' +import { sendTelemetryEvent } from 'common' +import { TelemetryEvent } from 'common/telemetry-constants' import { usePathname } from 'next/navigation' -import { useConsent } from 'ui-patterns/ConsentToast' -import { post } from './fetch/fetchWrappers' +import { useCallback } from 'react' +import { API_URL } from './constants' -type TelemetryEvent = { - action: string - category: string - label: string -} - -const noop = () => {} - -/** - * Sends a telemetry event to Logflare for tracking by the product team. - * - * Checks for user consent to telemetry before sending. - */ -const useSendTelemetryEvent = () => { +export function useSendTelemetryEvent() { const pathname = usePathname() - const { hasAcceptedConsent } = useConsent() - if (!hasAcceptedConsent) return noop - - const title = typeof document !== 'undefined' ? document?.title : '' - const referrer = typeof document !== 'undefined' ? document?.referrer : '' - - return (event: TelemetryEvent) => - post('/platform/telemetry/event', { - body: { - pathname, - action: event.action, - page_url: isBrowser ? window.location.href : '', - page_title: title, - ph: { - referrer, - language: navigator.language ?? 'en-US', - user_agent: navigator.userAgent, - search: isBrowser ? window.location.search : '', - viewport_height: isBrowser ? window.innerHeight : 0, - viewport_width: isBrowser ? window.innerWidth : 0, - }, - custom_properties: { - category: event.category, - label: event.label, - } as any, - }, - headers: { Version: '2' }, - credentials: 'include', - }) - .then(({ error }) => { - if (error) console.error(error) - }) - .catch((error) => console.error(error)) + return useCallback( + (event: TelemetryEvent) => { + return sendTelemetryEvent(API_URL, event, pathname) + }, + [pathname] + ) } - -export { useSendTelemetryEvent } diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx index 8268f751053..bfd8d5a3ada 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx @@ -2,9 +2,9 @@ import { ExternalLink, Eye, EyeOff, FlaskConical } from 'lucide-react' import Link from 'next/link' import { useEffect, useState } from 'react' +import { TelemetryActions } from 'common/telemetry-constants' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useFlag } from 'hooks/ui/useFlag' -import { TelemetryActions } from 'lib/constants/telemetry' import { useAppStateSnapshot } from 'state/app-state' import { Badge, Button, Modal, ScrollArea, cn } from 'ui' import { FEATURE_PREVIEWS, useFeaturePreviewContext } from './FeaturePreviewContext' diff --git a/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx index e48d17bfb4a..357ad0f78af 100644 --- a/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx +++ b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx @@ -2,6 +2,7 @@ import { ChevronDown } from 'lucide-react' import { HTMLAttributes, ReactNode, useState } from 'react' import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { getAddons } from 'components/interfaces/Billing/Subscription/Subscription.utils' import AlertError from 'components/ui/AlertError' import DatabaseSelector from 'components/ui/DatabaseSelector' @@ -10,7 +11,6 @@ import { usePoolingConfigurationQuery } from 'data/database/pooling-configuratio import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { TelemetryActions } from 'lib/constants/telemetry' import { pluckObjectFields } from 'lib/helpers' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx index a2e08a3938c..2e25afb4ee2 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx @@ -7,6 +7,7 @@ import { toast } from 'sonner' import z from 'zod' import { useWatch } from '@ui/components/shadcn/ui/form' +import { TelemetryActions } from 'common/telemetry-constants' import { urlRegex } from 'components/interfaces/Auth/Auth.constants' import EnableExtensionModal from 'components/interfaces/Database/Extensions/EnableExtensionModal' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' @@ -16,7 +17,6 @@ import { CronJob, useCronJobsQuery } from 'data/database-cron-jobs/database-cron import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { TelemetryActions } from 'lib/constants/telemetry' import { Button, Form_Shadcn_, diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobCard.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobCard.tsx index 24bab76d1f6..96ee47d5e91 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobCard.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobCard.tsx @@ -4,12 +4,12 @@ import Link from 'next/link' import { useState } from 'react' import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { CronJob } from 'data/database-cron-jobs/database-cron-jobs-query' import { useCronJobRunQuery } from 'data/database-cron-jobs/database-cron-jobs-run-query' import { useDatabaseCronJobToggleMutation } from 'data/database-cron-jobs/database-cron-jobs-toggle-mutation' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { TelemetryActions } from 'lib/constants/telemetry' import { Badge, Button, diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx index c323faf9dd6..68772da0d66 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/CronJobsTab.tsx @@ -2,13 +2,13 @@ import { Search } from 'lucide-react' import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' import { useState } from 'react' +import { TelemetryActions } from 'common/telemetry-constants' import { CreateCronJobSheet } from 'components/interfaces/Integrations/CronJobs/CreateCronJobSheet' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { CronJob, useCronJobsQuery } from 'data/database-cron-jobs/database-cron-jobs-query' import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { TelemetryActions } from 'lib/constants/telemetry' import { Button, Input, Sheet, SheetContent } from 'ui' import { CronJobCard } from './CronJobCard' import { DeleteCronJob } from './DeleteCronJob' diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/DeleteCronJob.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/DeleteCronJob.tsx index b8c6e020baa..5d7d8dd00a2 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/DeleteCronJob.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/DeleteCronJob.tsx @@ -1,10 +1,10 @@ import { toast } from 'sonner' +import { TelemetryActions } from 'common/telemetry-constants' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useDatabaseCronJobDeleteMutation } from 'data/database-cron-jobs/database-cron-jobs-delete-mutation' import { CronJob } from 'data/database-cron-jobs/database-cron-jobs-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { TelemetryActions } from 'lib/constants/telemetry' import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' diff --git a/apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx b/apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx index 476f81a4cc4..3b1731e059a 100644 --- a/apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx +++ b/apps/studio/components/interfaces/ProjectCreation/SchemaGenerator.tsx @@ -1,10 +1,10 @@ import { useChat } from 'ai/react' import { useEffect, useState } from 'react' +import { TelemetryActions } from 'common/telemetry-constants' import { Markdown } from 'components/interfaces/Markdown' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { BASE_PATH } from 'lib/constants' -import { TelemetryActions } from 'lib/constants/telemetry' import { AiIconAnimation, Button, Label_Shadcn_, Textarea } from 'ui' interface SupabaseService { diff --git a/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx index 0fb027cdeac..94ddde52839 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx @@ -1,11 +1,14 @@ import { zodResolver } from '@hookform/resolvers/zod' -import { ChevronDown, ExternalLink } from 'lucide-react' +import { useParams } from 'common' +import { ChevronDown } from 'lucide-react' import { Dispatch, SetStateAction, useState } from 'react' import { useForm } from 'react-hook-form' import * as z from 'zod' -import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' +import { DocsButton } from 'components/ui/DocsButton' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { Button, FormControl_Shadcn_, @@ -21,9 +24,6 @@ import { Switch, } from 'ui' import { RealtimeConfig } from '../useRealtimeMessages' -import { DocsButton } from 'components/ui/DocsButton' -import { TelemetryActions } from 'lib/constants/telemetry' -import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' interface ChooseChannelPopoverProps { config: RealtimeConfig diff --git a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx index 1d4a8dec622..10c10ad7c2c 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx @@ -1,10 +1,10 @@ import { PlayCircle, StopCircle } from 'lucide-react' import { Dispatch, SetStateAction } from 'react' +import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { TelemetryActions } from 'lib/constants/telemetry' -import { useParams } from 'common' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { ChooseChannelPopover } from './ChooseChannelPopover' import { RealtimeFilterPopover } from './RealtimeFilterPopover' diff --git a/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx b/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx index 7fd0fc4fd22..020ea5daf0d 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx @@ -1,14 +1,14 @@ import { X } from 'lucide-react' import { useMemo } from 'react' +import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import CopyButton from 'components/ui/CopyButton' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { Button, cn } from 'ui' import type { LogData } from './Messages.types' import { SelectedRealtimeMessagePanel } from './SelectedRealtimeMessagePanel' -import { TelemetryActions } from 'lib/constants/telemetry' -import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' -import { useParams } from 'common' export interface MessageSelectionProps { log: LogData | null diff --git a/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx b/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx index fbbfd14b83f..f7674bcca37 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx @@ -5,16 +5,16 @@ import { useEffect, useState } from 'react' import DataGrid, { Row } from 'react-data-grid' import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' +import { DocsButton } from 'components/ui/DocsButton' import ShimmerLine from 'components/ui/ShimmerLine' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { Button, IconBroadcast, IconDatabaseChanges, IconPresence, cn } from 'ui' import MessageSelection from './MessageSelection' import type { LogData } from './Messages.types' import NoChannelEmptyState from './NoChannelEmptyState' import { ColumnRenderer } from './RealtimeMessageColumnRenderer' -import { DocsButton } from 'components/ui/DocsButton' -import { TelemetryActions } from 'lib/constants/telemetry' -import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' export const isErrorLog = (l: LogData) => { return l.message === 'SYSTEM' && l.metadata?.status === 'error' diff --git a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx index a10173ec5f8..7225e6c228c 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx @@ -2,7 +2,10 @@ import { PlusCircle } from 'lucide-react' import Link from 'next/link' import { Dispatch, SetStateAction, useState } from 'react' +import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { Badge, Button, @@ -20,9 +23,6 @@ import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { RealtimeConfig } from '../useRealtimeMessages' import { FilterSchema } from './FilterSchema' import { FilterTable } from './FilterTable' -import { TelemetryActions } from 'lib/constants/telemetry' -import { useParams } from 'common' -import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' interface RealtimeFilterPopoverProps { config: RealtimeConfig diff --git a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover/index.tsx index ed740ec281d..a9851e6f622 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/RealtimeTokensPopover/index.tsx @@ -1,12 +1,12 @@ import { Dispatch, SetStateAction, useEffect, useRef } from 'react' import { toast } from 'sonner' +import { TelemetryActions } from 'common/telemetry-constants' import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector' import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query' import { getAPIKeys, useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { IS_PLATFORM } from 'lib/constants' -import { TelemetryActions } from 'lib/constants/telemetry' import { getRoleImpersonationJWT } from 'lib/role-impersonation' import { useRoleImpersonationStateSnapshot } from 'state/role-impersonation-state' import { RealtimeConfig } from '../useRealtimeMessages' diff --git a/apps/studio/components/interfaces/Realtime/Inspector/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/index.tsx index 0a4b0d52b0e..737534f40fc 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/index.tsx @@ -1,8 +1,8 @@ import { useParams } from 'common' import { useState } from 'react' +import { TelemetryActions } from 'common/telemetry-constants' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { TelemetryActions } from 'lib/constants/telemetry' import { Header } from './Header' import MessagesTable from './MessagesTable' import { SendMessageModal } from './SendMessageModal' diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx index 0314665f56a..90b707d9efc 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx @@ -9,6 +9,7 @@ import { toast } from 'sonner' import { useCompletion } from 'ai/react' import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { GridFooter } from 'components/ui/GridFooter' import { useSqlTitleGenerateMutation } from 'data/ai/sql-title-mutation' import { useEntityDefinitionsQuery } from 'data/database/entity-definitions-query' @@ -25,7 +26,6 @@ import { useSchemasForAi } from 'hooks/misc/useSchemasForAi' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { BASE_PATH, IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants' -import { TelemetryActions } from 'lib/constants/telemetry' import { detectOS, uuidv4 } from 'lib/helpers' import { useProfile } from 'lib/profile' import { wrapWithRoleImpersonation } from 'lib/role-impersonation' diff --git a/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx b/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx index d1fe7c57df9..f16788ddf32 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx @@ -4,11 +4,11 @@ import { useRouter } from 'next/router' import { toast } from 'sonner' import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.queries' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProject } from 'hooks/misc/useSelectedProject' -import { TelemetryActions } from 'lib/constants/telemetry' import { uuidv4 } from 'lib/helpers' import { useProfile } from 'lib/profile' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' diff --git a/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx b/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx index 85aa3b733ce..717b9dc9ed6 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx @@ -4,17 +4,17 @@ import { useRouter } from 'next/router' import { toast } from 'sonner' import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.queries' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { uuidv4 } from 'lib/helpers' import { useProfile } from 'lib/profile' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' import { createSqlSnippetSkeletonV2 } from '../SQLEditor.utils' import SQLCard from './SQLCard' -import { TelemetryActions } from 'lib/constants/telemetry' -import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' const SQLTemplates = () => { const router = useRouter() diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx index 56178a6db10..0d6bd02e820 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ResultsDropdown.tsx @@ -5,8 +5,11 @@ import { useMemo, useRef } from 'react' import { CSVLink } from 'react-csv' import { toast } from 'sonner' +import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' +import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { copyToClipboard } from 'lib/helpers' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' import { @@ -16,9 +19,6 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from 'ui' -import { TelemetryActions } from 'lib/constants/telemetry' -import { useParams } from 'common' -import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' export type ResultsDropdownProps = { id: string diff --git a/apps/studio/components/interfaces/SignIn/SignInForm.tsx b/apps/studio/components/interfaces/SignIn/SignInForm.tsx index 2d202b7ee90..85358caaa51 100644 --- a/apps/studio/components/interfaces/SignIn/SignInForm.tsx +++ b/apps/studio/components/interfaces/SignIn/SignInForm.tsx @@ -8,10 +8,10 @@ import { useRef, useState } from 'react' import { toast } from 'sonner' import { object, string } from 'yup' +import { TelemetryActions } from 'common/telemetry-constants' import { getMfaAuthenticatorAssuranceLevel } from 'data/profile/mfa-authenticator-assurance-level-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useLastSignIn } from 'hooks/misc/useLastSignIn' -import { TelemetryActions } from 'lib/constants/telemetry' import { auth, buildPathWithParams, getReturnToPath } from 'lib/gotrue' import { Button, Form, Input } from 'ui' import { LastSignInWrapper } from './LastSignInWrapper' diff --git a/apps/studio/components/interfaces/SignIn/SignInSSOForm.tsx b/apps/studio/components/interfaces/SignIn/SignInSSOForm.tsx index 93b2e16951a..fb5c2b11feb 100644 --- a/apps/studio/components/interfaces/SignIn/SignInSSOForm.tsx +++ b/apps/studio/components/interfaces/SignIn/SignInSSOForm.tsx @@ -5,10 +5,10 @@ import { useRef, useState } from 'react' import { toast } from 'sonner' import { object, string } from 'yup' +import { TelemetryActions } from 'common/telemetry-constants' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useLastSignIn } from 'hooks/misc/useLastSignIn' import { BASE_PATH } from 'lib/constants' -import { TelemetryActions } from 'lib/constants/telemetry' import { auth, buildPathWithParams } from 'lib/gotrue' import { Button, Form, Input } from 'ui' diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx index 4e04887e318..225f61a72aa 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx @@ -9,6 +9,7 @@ import { memo, useEffect, useMemo, useRef, useState } from 'react' import { toast } from 'sonner' import { useParams, useSearchParamsShallow } from 'common/hooks' +import { TelemetryActions } from 'common/telemetry-constants' import { subscriptionHasHipaaAddon } from 'components/interfaces/Billing/Subscription/Subscription.utils' import { Markdown } from 'components/interfaces/Markdown' import OptInToOpenAIToggle from 'components/interfaces/Organization/GeneralSettings/OptInToOpenAIToggle' @@ -25,7 +26,6 @@ import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { useFlag } from 'hooks/ui/useFlag' import { BASE_PATH, IS_PLATFORM, OPT_IN_TAGS } from 'lib/constants' -import { TelemetryActions } from 'lib/constants/telemetry' import uuidv4 from 'lib/uuid' import { useAppStateSnapshot } from 'state/app-state' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' diff --git a/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx b/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx index 94ebd7650d4..3f62eaf08e1 100644 --- a/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx @@ -2,11 +2,11 @@ import { useRouter } from 'next/router' import { DragEvent, memo, ReactNode, useContext, useEffect, useMemo, useRef } from 'react' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { TelemetryActions } from 'common/telemetry-constants' import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useFlag } from 'hooks/ui/useFlag' -import { TelemetryActions } from 'lib/constants/telemetry' import { useProfile } from 'lib/profile' import { Dashboards } from 'types' import { cn, CodeBlock, CodeBlockLang } from 'ui' diff --git a/apps/studio/components/ui/QueryBlock/EditQueryButton.tsx b/apps/studio/components/ui/QueryBlock/EditQueryButton.tsx index a850a67630a..28f6c6cae98 100644 --- a/apps/studio/components/ui/QueryBlock/EditQueryButton.tsx +++ b/apps/studio/components/ui/QueryBlock/EditQueryButton.tsx @@ -2,10 +2,10 @@ import { Edit } from 'lucide-react' import { useRouter } from 'next/router' import { useParams } from 'common' +import { TelemetryActions } from 'common/telemetry-constants' import { DiffType } from 'components/interfaces/SQLEditor/SQLEditor.types' import useNewQuery from 'components/interfaces/SQLEditor/hooks' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { TelemetryActions } from 'lib/constants/telemetry' import Link from 'next/link' import { ComponentProps } from 'react' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' diff --git a/apps/studio/data/telemetry/send-event-mutation.ts b/apps/studio/data/telemetry/send-event-mutation.ts index f8d64c20256..621e1aaa6f5 100644 --- a/apps/studio/data/telemetry/send-event-mutation.ts +++ b/apps/studio/data/telemetry/send-event-mutation.ts @@ -1,97 +1,23 @@ import { useMutation, UseMutationOptions } from '@tanstack/react-query' -import { components } from 'api-types' -import { isBrowser, LOCAL_STORAGE_KEYS } from 'common' -import { handleError, post } from 'data/fetchers' -import { IS_PLATFORM } from 'lib/constants' -import { - ConnectionStringCopiedEvent, - CronJobCreateClickedEvent, - CronJobCreatedEvent, - CronJobDeleteClickedEvent, - CronJobDeletedEvent, - CronJobHistoryClickedEvent, - CronJobUpdateClickedEvent, - CronJobUpdatedEvent, - FeaturePreviewsClickedEvent, - FeaturePreviewEnabledEvent, - FeaturePreviewDisabledEvent, - SqlEditorQuickstartClickedEvent, - SqlEditorTemplateClickedEvent, - SqlEditorResultDownloadCsvClickedEvent, - SqlEditorResultCopyMarkdownClickedEvent, - SqlEditorResultCopyJsonClickedEvent, - SignUpEvent, - SignInEvent, - RealtimeInspectorListenChannelClickedEvent, - RealtimeInspectorBroadcastSentEvent, - RealtimeInspectorMessageClickedEvent, - RealtimeInspectorCopyMessageClickedEvent, - RealtimeInspectorFiltersAppliedEvent, - RealtimeInspectorDatabaseRoleUpdatedEvent, - ProjectCreationInitialStepPromptIntendedEvent, - ProjectCreationInitialStepSubmittedEvent, - ProjectCreationSecondStepPromptIntendedEvent, - ProjectCreationSecondStepSubmittedEvent, - AssistantPromptSubmittedEvent, - AssistantDebugSubmittedEvent, - AssistantSuggestionRunQueryClickedEvent, - AssistantSqlDiffHandlerEvaluatedEvent, - AssistantEditInSqlEditorClickedEvent, -} from 'lib/constants/telemetry' +import { sendTelemetryEvent } from 'common' +import { TelemetryEvent } from 'common/telemetry-constants' +import { handleError } from 'data/fetchers' +import { API_URL } from 'lib/constants' import { useRouter } from 'next/router' import type { ResponseError } from 'types' -export type SendEventVariables = - | SignUpEvent - | SignInEvent - | ConnectionStringCopiedEvent - | CronJobCreatedEvent - | CronJobUpdatedEvent - | CronJobDeletedEvent - | CronJobCreateClickedEvent - | CronJobUpdateClickedEvent - | CronJobDeleteClickedEvent - | CronJobHistoryClickedEvent - | FeaturePreviewsClickedEvent - | FeaturePreviewEnabledEvent - | FeaturePreviewDisabledEvent - | ProjectCreationInitialStepPromptIntendedEvent - | ProjectCreationInitialStepSubmittedEvent - | ProjectCreationSecondStepPromptIntendedEvent - | ProjectCreationSecondStepSubmittedEvent - | RealtimeInspectorListenChannelClickedEvent - | RealtimeInspectorBroadcastSentEvent - | RealtimeInspectorMessageClickedEvent - | RealtimeInspectorCopyMessageClickedEvent - | RealtimeInspectorFiltersAppliedEvent - | RealtimeInspectorDatabaseRoleUpdatedEvent - | SqlEditorQuickstartClickedEvent - | SqlEditorTemplateClickedEvent - | SqlEditorResultDownloadCsvClickedEvent - | SqlEditorResultCopyMarkdownClickedEvent - | SqlEditorResultCopyJsonClickedEvent - | AssistantPromptSubmittedEvent - | AssistantDebugSubmittedEvent - | AssistantSuggestionRunQueryClickedEvent - | AssistantSqlDiffHandlerEvaluatedEvent - | AssistantEditInSqlEditorClickedEvent +interface SendEventVariables { + event: TelemetryEvent + pathname?: string +} -type SendEventPayload = components['schemas']['TelemetryEventBodyV2Dto'] - -export async function sendEvent({ body }: { body: SendEventPayload }) { - const consent = - (typeof window !== 'undefined' - ? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) - : null) === 'true' - - if (!consent || !IS_PLATFORM) return undefined - - const headers = { Version: '2' } - const { data, error } = await post(`/platform/telemetry/event`, { body, headers }) - - if (error) handleError(error) - return data +export async function sendEvent({ event, pathname }: SendEventVariables) { + try { + await sendTelemetryEvent(API_URL, event, pathname) + } catch (error) { + handleError(error) + } } type SendEventData = Awaited> @@ -100,39 +26,12 @@ export const useSendEventMutation = ({ onSuccess, onError, ...options -}: Omit< - UseMutationOptions, - 'mutationFn' -> = {}) => { +}: Omit, 'mutationFn'> = {}) => { const router = useRouter() - const title = typeof document !== 'undefined' ? document?.title : '' - const referrer = typeof document !== 'undefined' ? document?.referrer : '' - - return useMutation( - (vars) => { - const { action } = vars - const properties = 'properties' in vars ? vars.properties : {} - const groups = 'groups' in vars ? vars.groups : {} - - const body: SendEventPayload = { - action, - page_url: window.location.href, - page_title: title, - pathname: router.pathname, - ph: { - referrer, - language: router?.locale ?? 'en-US', - user_agent: navigator.userAgent, - search: window.location.search, - viewport_height: isBrowser ? window.innerHeight : 0, - viewport_width: isBrowser ? window.innerWidth : 0, - }, - custom_properties: properties as any, - groups, - } - - return sendEvent({ body }) + return useMutation( + (event) => { + return sendEvent({ event, pathname: router.pathname }) }, { async onSuccess(data, variables, context) { diff --git a/apps/studio/data/telemetry/send-identify-mutation.ts b/apps/studio/data/telemetry/send-identify-mutation.ts deleted file mode 100644 index 3d64cc2c899..00000000000 --- a/apps/studio/data/telemetry/send-identify-mutation.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { useMutation, UseMutationOptions } from '@tanstack/react-query' - -import { components } from 'api-types' -import { LOCAL_STORAGE_KEYS } from 'common' -import { handleError, post } from 'data/fetchers' -import { Profile } from 'data/profile/types' -import { IS_PLATFORM } from 'lib/constants' -import type { ResponseError } from 'types' - -type SendIdentify = components['schemas']['TelemetryIdentifyBodyV2'] - -export type SendIdentifyVariables = { - slug?: string - ref?: string - user: Profile -} - -type SendIdentifyPayload = any - -export async function sendIdentify({ body }: { body: SendIdentifyPayload }) { - const consent = - (typeof window !== 'undefined' - ? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) - : null) === 'true' - - if (!consent || !IS_PLATFORM) return undefined - - const headers = { Version: '2' } - const { data, error } = await post(`/platform/telemetry/identify`, { body, headers }) - if (error) handleError(error) - return data -} - -type SendIdentifyData = Awaited> - -export const useSendIdentifyMutation = ({ - onSuccess, - onError, - ...options -}: Omit< - UseMutationOptions, - 'mutationFn' -> = {}) => { - return useMutation( - (vars) => { - const { user, slug, ref } = vars - - const body: SendIdentify = { - user_id: user.gotrue_id, - organization_slug: slug, - project_ref: ref, - } - - return sendIdentify({ body }) - }, - { - async onSuccess(data, variables, context) { - await onSuccess?.(data, variables, context) - }, - async onError(data, variables, context) { - if (onError === undefined) { - console.error(`Failed to send Telemetry identify: ${data.message}`) - } else { - onError(data, variables, context) - } - }, - ...options, - } - ) -} diff --git a/apps/studio/lib/profile.tsx b/apps/studio/lib/profile.tsx index c73e5180b9d..3f8ac40a503 100644 --- a/apps/studio/lib/profile.tsx +++ b/apps/studio/lib/profile.tsx @@ -3,15 +3,14 @@ import { useRouter } from 'next/router' import { PropsWithChildren, createContext, useContext, useMemo } from 'react' import { toast } from 'sonner' +import { TelemetryActions } from 'common/telemetry-constants' import { usePermissionsQuery } from 'data/permissions/permissions-query' import { useProfileCreateMutation } from 'data/profile/profile-create-mutation' import { useProfileQuery } from 'data/profile/profile-query' import type { Profile } from 'data/profile/types' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useSendIdentifyMutation } from 'data/telemetry/send-identify-mutation' -import { ResponseError } from 'types' +import type { ResponseError } from 'types' import { useSignOut } from './auth' -import { TelemetryActions } from './constants/telemetry' export type ProfileContextType = { profile: Profile | undefined @@ -35,7 +34,6 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => { const signOut = useSignOut() const { mutate: sendEvent } = useSendEventMutation() - const { mutate: sendIdentify } = useSendIdentifyMutation() const { mutate: createProfile, isLoading: isCreatingProfile } = useProfileCreateMutation({ onSuccess: () => { sendEvent({ action: TelemetryActions.SIGN_UP, properties: { category: 'conversion' } }) @@ -52,9 +50,6 @@ export const ProfileProvider = ({ children }: PropsWithChildren<{}>) => { isSuccess, } = useProfileQuery({ enabled: isLoggedIn, - onSuccess(profile) { - sendIdentify({ user: profile }) - }, onError(err) { // if the user does not yet exist, create a profile for them if (err.message === "User's profile not found") { diff --git a/apps/studio/pages/new/v2/[slug].tsx b/apps/studio/pages/new/v2/[slug].tsx index 515c7a476b0..68a344d7ff3 100644 --- a/apps/studio/pages/new/v2/[slug].tsx +++ b/apps/studio/pages/new/v2/[slug].tsx @@ -61,7 +61,7 @@ import { PROJECT_STATUS, PROVIDERS, } from 'lib/constants' -import { TelemetryActions } from 'lib/constants/telemetry' +import { TelemetryActions } from 'common/telemetry-constants' import passwordStrength from 'lib/password-strength' import { generateStrongPassword } from 'lib/project' import type { CloudProvider } from 'shared-data' diff --git a/apps/studio/pages/sign-in-mfa.tsx b/apps/studio/pages/sign-in-mfa.tsx index 18fe5f4384a..ba0f0fde5cf 100644 --- a/apps/studio/pages/sign-in-mfa.tsx +++ b/apps/studio/pages/sign-in-mfa.tsx @@ -9,7 +9,7 @@ import { Loading } from 'components/ui/Loading' import { auth, buildPathWithParams, getAccessToken, getReturnToPath } from 'lib/gotrue' import type { NextPageWithLayout } from 'types' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { TelemetryActions } from 'lib/constants/telemetry' +import { TelemetryActions } from 'common/telemetry-constants' const SignInMfaPage: NextPageWithLayout = () => { const router = useRouter() diff --git a/apps/www/components/Hero/Hero.tsx b/apps/www/components/Hero/Hero.tsx index 073d584b9a0..e0cd210cabe 100644 --- a/apps/www/components/Hero/Hero.tsx +++ b/apps/www/components/Hero/Hero.tsx @@ -1,19 +1,12 @@ import Link from 'next/link' -import { useRouter } from 'next/router' -import { useTelemetryProps } from 'common/hooks/useTelemetryProps' -import gaEvents from '~/lib/gaEvents' -import Telemetry, { TelemetryEvent } from '~/lib/telemetry' -import { Button } from 'ui' +import { Button } from 'ui' import SectionContainer from '~/components/Layouts/SectionContainer' -import AnnouncementBadge from '~/components/Announcement/Badge' +import { TelemetryActions } from 'common/telemetry-constants' +import { useSendTelemetryEvent } from '~/lib/telemetry' const Hero = () => { - const router = useRouter() - const telemetryProps = useTelemetryProps() - const sendTelemetryEvent = async (event: TelemetryEvent) => { - await Telemetry.sendEvent(event, telemetryProps, router) - } + const sendTelemetryEvent = useSendTelemetryEvent() return (
@@ -39,7 +32,11 @@ const Hero = () => { sendTelemetryEvent(gaEvents['www_hp_hero_startProject'])} + onClick={() => + sendTelemetryEvent({ + action: TelemetryActions.HOMEPAGE_HERO_START_PROJECT_CLICKED, + }) + } > Start your project @@ -48,7 +45,11 @@ const Hero = () => { sendTelemetryEvent(gaEvents['www_hp_hero_requestDemo'])} + onClick={() => + sendTelemetryEvent({ + action: TelemetryActions.HOMEPAGE_HERO_REQUEST_DEMO_CLICKED, + }) + } > Request a demo diff --git a/apps/www/components/Hero/HeroFrameworks.tsx b/apps/www/components/Hero/HeroFrameworks.tsx index 459c65fa9f2..a1f8d38e9d1 100644 --- a/apps/www/components/Hero/HeroFrameworks.tsx +++ b/apps/www/components/Hero/HeroFrameworks.tsx @@ -1,18 +1,17 @@ -import React, { useState } from 'react' -import Link from 'next/link' -import { useRouter } from 'next/router' +import { useBreakpoint } from 'common' import { AnimatePresence, motion } from 'framer-motion' -import { useBreakpoint, useTelemetryProps } from 'common' +import Link from 'next/link' +import { useState } from 'react' import { cn } from 'ui' -import Telemetry from '~/lib/telemetry' import { EASE_IN, EASE_OUT } from '~/lib/animations' +import { TelemetryActions } from 'common/telemetry-constants' +import { useSendTelemetryEvent } from '~/lib/telemetry' import SectionContainer from '../Layouts/SectionContainer' type Framework = { name: string icon: string docs: string - gaEvent: string } const frameworks: Framework[] = [ @@ -20,61 +19,51 @@ const frameworks: Framework[] = [ name: 'React', icon: 'M45.74 23.6983C45.2739 23.5379 44.7909 23.3861 44.2937 23.2426C44.3754 22.909 44.4504 22.5798 44.5171 22.2561C45.6119 16.9418 44.8961 12.6605 42.4518 11.2509C40.1079 9.89927 36.2748 11.3085 32.4035 14.6776C32.0313 15.0016 31.6579 15.3446 31.2848 15.704C31.0362 15.4662 30.7879 15.2364 30.5403 15.0165C26.4831 11.4141 22.4164 9.89599 19.9744 11.3097C17.6329 12.6652 16.9394 16.69 17.9249 21.7265C18.0201 22.2129 18.1313 22.7097 18.2571 23.2148C17.6816 23.3782 17.1259 23.5524 16.5943 23.7377C11.8376 25.3961 8.7998 27.9952 8.7998 30.6911C8.7998 33.4755 12.0609 36.2683 17.0153 37.9617C17.4063 38.0953 17.812 38.2217 18.2301 38.3416C18.0944 38.8879 17.9763 39.4232 17.8773 39.9454C16.9376 44.8944 17.6714 48.8242 20.0068 50.1711C22.4189 51.5622 26.4673 50.1324 30.4093 46.6865C30.7209 46.4141 31.0336 46.1253 31.3469 45.8225C31.7529 46.2135 32.1582 46.5835 32.5615 46.9306C36.3798 50.2164 40.151 51.5432 42.4842 50.1925C44.894 48.7975 45.6772 44.576 44.6604 39.4399C44.5828 39.0476 44.4924 38.6469 44.3909 38.239C44.6752 38.155 44.9543 38.0682 45.2265 37.978C50.3771 36.2715 53.7282 33.5127 53.7282 30.6911C53.7282 27.9854 50.5924 25.3688 45.74 23.6983ZM44.6228 36.1561C44.3772 36.2375 44.1251 36.3161 43.8682 36.3923C43.2996 34.5922 42.5322 32.6781 41.5931 30.7005C42.4893 28.7699 43.227 26.8803 43.7797 25.0919C44.2393 25.2249 44.6854 25.3651 45.1152 25.5132C49.2728 26.9444 51.8089 29.0605 51.8089 30.6911C51.8089 32.4279 49.07 34.6826 44.6228 36.1561ZM42.7776 39.8126C43.2272 42.0837 43.2914 44.1371 42.9936 45.7423C42.726 47.1847 42.1878 48.1463 41.5225 48.5315C40.1066 49.351 37.0787 48.2857 33.8132 45.4757C33.4388 45.1535 33.0618 44.8096 32.6835 44.4455C33.9495 43.061 35.2147 41.4514 36.4495 39.6638C38.6215 39.4711 40.6735 39.156 42.5344 38.7258C42.626 39.0955 42.7074 39.4581 42.7776 39.8126ZM24.1169 48.3898C22.7336 48.8784 21.6318 48.8924 20.9658 48.5084C19.5486 47.691 18.9594 44.5358 19.7631 40.3033C19.8551 39.8186 19.9647 39.3207 20.091 38.8118C21.9314 39.2187 23.9684 39.5116 26.1456 39.6881C27.3887 41.4373 28.6905 43.0452 30.0024 44.453C29.7157 44.7297 29.4302 44.9931 29.1463 45.2413C27.4032 46.7651 25.6564 47.8461 24.1169 48.3898ZM17.6361 36.1455C15.4453 35.3967 13.6361 34.4235 12.396 33.3616C11.2817 32.4073 10.7191 31.4599 10.7191 30.6911C10.7191 29.0551 13.1581 26.9684 17.226 25.5501C17.7196 25.378 18.2363 25.2158 18.7725 25.0635C19.3347 26.8923 20.0722 28.8043 20.9623 30.7378C20.0607 32.7 19.3128 34.6425 18.745 36.4927C18.3628 36.3829 17.9924 36.2672 17.6361 36.1455ZM19.8085 21.3579C18.9642 17.0428 19.5249 13.7876 20.936 12.9708C22.4391 12.1006 25.7628 13.3413 29.2659 16.4518C29.4898 16.6506 29.7146 16.8587 29.9401 17.074C28.6347 18.4756 27.3448 20.0714 26.1127 21.8103C23.9997 22.0061 21.977 22.3208 20.1174 22.742C20.0004 22.2717 19.8969 21.8097 19.8085 21.3579ZM39.1886 26.1433C38.744 25.3754 38.2876 24.6257 37.8223 23.8964C39.2558 24.0777 40.6293 24.3182 41.9191 24.6126C41.5318 25.8536 41.0492 27.1511 40.4811 28.4813C40.0735 27.7076 39.6425 26.9275 39.1886 26.1433ZM31.2854 18.4456C32.1707 19.4047 33.0573 20.4756 33.9293 21.6374C33.0506 21.5959 32.161 21.5743 31.264 21.5743C30.3755 21.5743 29.4925 21.5954 28.6192 21.6362C29.4921 20.4852 30.3863 19.4158 31.2854 18.4456ZM23.3317 26.1566C22.8876 26.9267 22.4645 27.7025 22.0634 28.4799C21.5045 27.1543 21.0263 25.8509 20.6357 24.5923C21.9176 24.3054 23.2846 24.0709 24.7089 23.8931C24.2371 24.6291 23.7769 25.3843 23.3317 26.1564V26.1566ZM24.75 37.626C23.2783 37.4618 21.8908 37.2394 20.6093 36.9604C21.0061 35.6793 21.4948 34.3481 22.0655 32.994C22.4677 33.7707 22.8925 34.5469 23.3393 35.3187H23.3393C23.7945 36.1049 24.266 36.875 24.75 37.626ZM31.3385 43.0719C30.4289 42.0904 29.5215 41.0047 28.6353 39.8368C29.4956 39.8706 30.3726 39.8879 31.264 39.8879C32.1798 39.8879 33.085 39.8672 33.9761 39.8276C33.1012 41.0164 32.2178 42.1038 31.3385 43.0719ZM40.4994 32.9249C41.0999 34.2937 41.6061 35.618 42.0081 36.8772C40.7054 37.1744 39.2989 37.4138 37.8171 37.5916C38.2835 36.8525 38.7439 36.0899 39.1963 35.3055C39.6539 34.5118 40.0885 33.717 40.4994 32.9249ZM37.5337 34.3466C36.8314 35.5643 36.1104 36.7268 35.3784 37.8241C34.0452 37.9194 32.6678 37.9685 31.264 37.9685C29.8659 37.9685 28.5058 37.9251 27.1962 37.8401C26.4347 36.7284 25.698 35.5625 25.0002 34.3571H25.0004C24.3044 33.155 23.6638 31.9427 23.0834 30.7372C23.6636 29.5289 24.3025 28.3152 24.9945 27.1152L24.9944 27.1155C25.6882 25.9123 26.4184 24.7521 27.1729 23.6473C28.509 23.5463 29.8792 23.4936 31.2639 23.4936H31.264C32.655 23.4936 34.0269 23.5467 35.3626 23.6486C36.1056 24.7453 36.8308 25.9017 37.5274 27.1051C38.2319 28.3219 38.879 29.5275 39.4642 30.7099C38.8808 31.9126 38.2351 33.1303 37.5337 34.3466ZM41.4931 12.9137C42.9976 13.7813 43.5826 17.2804 42.6374 21.8688C42.5771 22.1615 42.5092 22.4597 42.4354 22.762C40.5715 22.3319 38.5474 22.0118 36.4282 21.813C35.1937 20.055 33.9143 18.4567 32.6302 17.0731C32.9755 16.741 33.3202 16.4243 33.6636 16.1254C36.9805 13.2388 40.0806 12.0991 41.4931 12.9137ZM31.264 26.6791C33.4797 26.6791 35.276 28.4753 35.276 30.6911C35.276 32.9068 33.4797 34.703 31.264 34.703C29.0483 34.703 27.252 32.9068 27.252 30.6911C27.252 28.4753 29.0483 26.6791 31.264 26.6791Z', docs: '/docs/guides/getting-started/quickstarts/reactjs', - gaEvent: 'www_hp_hero_frameworks_react', }, { name: 'Next.js', icon: 'M42.3148 48.6796C38.9009 50.9525 34.8014 52.2771 30.3924 52.2771C18.4957 52.2771 8.85156 42.6329 8.85156 30.7362C8.85156 18.8395 18.4957 9.19531 30.3924 9.19531C42.2891 9.19531 51.9333 18.8395 51.9333 30.7362C51.9333 37.1564 49.1245 42.9207 44.6688 46.8671L39.5552 40.2803V21.8278H36.584V36.4531L25.2299 21.8278H21.4808V39.6473H24.4801V25.6368L42.3148 48.6796Z', docs: '/docs/guides/getting-started/quickstarts/nextjs', - gaEvent: 'www_hp_hero_frameworks_nextjs', }, { name: 'RedwoodJS', icon: 'M21.7716 14.6475L31.2847 21.1184C31.4993 21.2609 31.7501 21.3389 32.0076 21.3434C32.2654 21.3415 32.5167 21.2633 32.7304 21.1184L42.2508 14.6257C42.6207 14.3618 42.8247 13.9218 42.7876 13.4678C42.7505 13.0139 42.4779 12.6131 42.0701 12.4131L32.5569 7.71949C32.1961 7.545 31.7757 7.545 31.4147 7.71949L21.9306 12.4131C21.5154 12.6141 21.2392 13.0227 21.2063 13.4841C21.1735 13.9455 21.3891 14.3893 21.7716 14.6475ZM35.2389 23.1497C35.2396 23.5789 35.4504 23.9801 35.8027 24.2233L43.4291 29.4176C43.9173 29.7529 44.5705 29.7111 45.0123 29.316L51.4098 23.614C51.6948 23.3601 51.8531 22.9925 51.8419 22.6102C51.8309 22.2279 51.6513 21.8702 51.3519 21.6335L45.2436 16.7658C44.7995 16.4143 44.1802 16.3908 43.711 16.7078L35.8027 22.0978C35.4566 22.3366 35.2466 22.7283 35.2389 23.1497ZM16.2704 30.2155C16.5786 30.4914 16.7371 30.8984 16.6969 31.311C16.6569 31.7258 16.4164 32.0946 16.0536 32.2975L11.4994 35.0179C11.0377 35.2929 10.4548 35.2533 10.0344 34.9183C9.61395 34.5834 9.44327 34.0226 9.60544 33.509L11.2898 28.2278C11.4219 27.8093 11.7568 27.4869 12.1789 27.3718C12.6007 27.2494 13.0555 27.3567 13.3789 27.6547L16.2704 30.2155ZM40.9712 30.7668L32.7377 25.1519C32.3001 24.8585 31.7295 24.8585 31.2919 25.1519L23.0582 30.7668C22.7308 30.9927 22.5234 31.3557 22.4945 31.7534C22.4731 32.154 22.6323 32.5428 22.9282 32.8126L31.1545 40.1468C31.3918 40.3577 31.6979 40.4738 32.0148 40.4732C32.3315 40.4731 32.6375 40.3571 32.875 40.1468L41.1014 32.8126C41.3981 32.544 41.5553 32.1535 41.5279 31.7534C41.5049 31.3557 41.2993 30.9912 40.9712 30.7668ZM19.0101 29.316L12.6199 23.614C12.3323 23.3537 12.1736 22.9795 12.1861 22.5911C12.1931 22.2083 12.3708 21.8488 12.6705 21.6118L18.7788 16.715C19.2259 16.3645 19.8465 16.3411 20.3185 16.657L28.2197 22.047C28.5863 22.2867 28.8075 22.696 28.8075 23.1352C28.8075 23.5743 28.5863 23.9837 28.2197 24.2233L20.6005 29.4175C20.1093 29.7514 19.4552 29.7097 19.0101 29.316ZM51.8218 37.5062L45.3158 33.625C44.8326 33.3349 44.2176 33.3937 43.7977 33.7702L35.8461 40.8432C35.5106 41.1418 35.3531 41.5933 35.4296 42.0366C35.5063 42.4798 35.8059 42.8518 36.2219 43.0196L47.2531 47.4738C47.4044 47.5365 47.5666 47.5686 47.7302 47.5681C48.2422 47.5697 48.7069 47.2683 48.9158 46.7991L52.335 39.1675C52.6132 38.5636 52.3917 37.8462 51.8218 37.5062ZM52.7253 28.2278L54.4097 33.509H54.3952C54.5198 33.904 54.449 34.3351 54.2047 34.669C53.9604 35.0031 53.572 35.2 53.159 35.1993C52.9275 35.2 52.7 35.1374 52.5013 35.018L47.9399 32.2975C47.5844 32.0904 47.3524 31.722 47.3182 31.311C47.2755 30.8981 47.4344 30.49 47.7447 30.2155L50.6362 27.6474C50.9629 27.3558 51.4143 27.2493 51.8362 27.3645C52.258 27.4846 52.5918 27.8088 52.7253 28.2278ZM28.5955 42.033C28.6733 41.5915 28.5174 41.1412 28.1835 40.8432L20.2318 33.7702C19.8119 33.3937 19.1969 33.3349 18.7138 33.625L12.2078 37.5062C11.6445 37.8477 11.4216 38.556 11.6873 39.1603L15.1138 46.7919C15.3977 47.4283 16.1316 47.7261 16.7764 47.4666L27.8004 43.0124C28.2165 42.846 28.5173 42.4755 28.5955 42.033ZM32.4991 44.2093L41.3472 47.7785C41.7799 47.963 42.0787 48.3684 42.128 48.8376C42.184 49.3127 41.9722 49.7795 41.5785 50.0491L32.7232 56.1791C32.511 56.3277 32.2591 56.4086 32.0003 56.4113C31.7418 56.4073 31.4903 56.3265 31.2774 56.1791L22.4294 50.0491C22.0343 49.7802 21.82 49.3139 21.8728 48.8376C21.9318 48.3636 22.2414 47.9586 22.6824 47.7785L31.5305 44.2093C31.8416 44.0856 32.188 44.0856 32.4991 44.2093Z', docs: '/docs/guides/getting-started/quickstarts/redwoodjs', - gaEvent: 'www_hp_hero_frameworks_redwoodjs', }, { name: 'Flutter', icon: `M46.5067 10.3828L34.3509 10.3962L14.75 29.9971L20.7974 36.0519L26.1125 30.7666L46.5067 10.3828Z M34.6996 28.4653C34.5272 28.4573 34.3493 28.4491 34.2378 28.5965L23.7856 39.0471L29.7894 45.0142L29.7825 45.021L34.079 49.3212C34.1072 49.3462 34.1352 49.3741 34.1637 49.4026C34.2813 49.5201 34.4074 49.6462 34.5895 49.6055C36.5743 49.601 38.5591 49.6017 40.544 49.6025C42.529 49.6032 44.5142 49.604 46.4998 49.5995L35.9333 39.0234L46.4963 28.467L34.906 28.464C34.8415 28.4719 34.7711 28.4686 34.6996 28.4653Z`, docs: '/docs/guides/getting-started/quickstarts/flutter', - gaEvent: 'www_hp_hero_frameworks_flutter', }, { name: 'Kotlin', icon: `M51.7395 51.7398H12.2598V12.2601H51.7395L31.591 31.7137L51.7395 51.7398Z`, docs: '/docs/guides/getting-started/quickstarts/kotlin', - gaEvent: 'www_hp_hero_frameworks_kotlin', }, { name: 'Svelte', icon: 'M29.9094 11.2292C35.956 7.37668 44.3187 9.17299 48.553 15.2334H48.5532C50.5831 18.0746 51.3826 21.614 50.771 25.0519C50.4778 26.677 49.8581 28.2259 48.9493 29.6047C50.2752 32.1335 50.7201 35.0322 50.2136 37.8422C49.6086 41.2154 47.6106 44.1777 44.7096 46.0024L34.0903 52.7707C28.0445 56.623 19.6818 54.8274 15.4466 48.7665C13.4171 45.9251 12.6176 42.3859 13.2288 38.948C13.5223 37.3227 14.1422 35.7738 15.0512 34.3949C13.7247 31.8665 13.2794 28.9677 13.786 26.1577C14.3913 22.7845 16.3893 19.8223 19.29 17.9974L29.9094 11.2292ZM19.8146 45.9861C21.8311 48.8931 25.4469 50.2333 28.8709 49.343H28.8708C29.6345 49.139 30.3624 48.8192 31.0293 48.3946L41.6512 41.6252C43.396 40.5274 44.5979 38.7455 44.9622 36.7164C45.33 34.6483 44.8489 32.5192 43.6278 30.8101C41.6113 27.9032 37.9955 26.5629 34.5715 27.4531C33.8084 27.6571 33.081 27.9768 32.4147 28.4012L28.3617 30.9842C28.1601 31.1125 27.9401 31.2092 27.7093 31.271C26.6776 31.5384 25.5887 31.1342 24.9815 30.2584C24.614 29.7429 24.4693 29.1012 24.5801 28.4779C24.6899 27.8669 25.0519 27.3302 25.5774 26.9996L36.2002 20.2298C36.4017 20.1015 36.6218 20.0048 36.8526 19.9431C37.8838 19.6754 38.9725 20.0795 39.5793 20.9551C39.9039 21.4146 40.0556 21.974 40.0078 22.5345L39.9714 22.9285L40.3662 23.0484C41.8596 23.4989 43.265 24.2014 44.5218 25.1254L45.0657 25.5245L45.2658 24.9145C45.3729 24.59 45.4577 24.2586 45.5196 23.9225C45.8873 21.8544 45.4063 19.7254 44.1852 18.0162C42.1687 15.1093 38.553 13.7691 35.129 14.6593C34.3653 14.8633 33.6374 15.1832 32.9705 15.6077L22.3487 22.3777C20.6036 23.475 19.4016 25.2568 19.0376 27.2858C18.6699 29.3539 19.1509 31.4829 20.372 33.192C22.3885 36.099 26.0043 37.4392 29.4283 36.549C30.1914 36.345 30.9188 36.0256 31.5853 35.6017L35.6389 33.0177C35.8402 32.8895 36.06 32.7929 36.2905 32.7311C37.3221 32.4637 38.4111 32.868 39.0183 33.7438C39.3857 34.2592 39.5306 34.9007 39.4205 35.524C39.3102 36.1352 38.948 36.6718 38.4224 37.0028L27.7996 43.7722C27.5981 43.9006 27.378 43.9973 27.1471 44.059C26.116 44.3266 25.0273 43.9225 24.4204 43.0469C24.0955 42.5876 23.9438 42.0281 23.992 41.4675L24.0284 41.0735L23.6336 40.9537C22.1404 40.5032 20.7351 39.8011 19.4783 38.8776L18.934 38.4778L18.734 39.0878C18.6266 39.4122 18.5418 39.7437 18.4801 40.0798C18.1125 42.1479 18.5935 44.277 19.8146 45.9861Z', docs: '/docs/guides/getting-started/quickstarts/sveltekit', - gaEvent: 'www_hp_hero_frameworks_svelte', }, { name: 'SolidJS', icon: 'M26.7069 8.48157C39.8712 5.45973 56.8336 18.3919 56.8336 18.3919L51.0722 28.1073L50.9621 28.0856C51.0348 28.141 51.0722 28.1726 51.0722 28.1726C51.0681 28.1647 50.7083 27.8867 50.0561 27.4261C46.2099 24.7104 32.1917 15.6482 20.9454 18.2298C20.8906 18.2436 20.8351 18.2574 20.7793 18.2714C20.4417 18.3556 20.0905 18.4432 19.7867 18.5547C18.0673 19.116 16.7102 20.0163 15.7606 21.1271L15.7312 21.1213L20.5914 12.7706C20.6522 12.6724 20.7085 12.5694 20.7654 12.4654C20.8589 12.2942 20.9541 12.12 21.0742 11.9583C22.0076 10.5936 23.5204 9.45636 25.5481 8.8065C25.852 8.69497 26.2032 8.60737 26.5408 8.52316L26.5433 8.52253C26.5983 8.50882 26.6529 8.49519 26.7069 8.48157Z M19.46 32.0592L32.05 27.9448C35.8485 26.7049 40.3864 27.4446 43.8122 29.5562C43.7796 29.5338 44.8294 30.2289 44.9407 30.3542L49.7055 28.4801C49.3614 28.2446 48.0959 27.3256 47.6565 27.0394C45.8866 25.8866 43.3927 24.3844 40.4974 22.978C34.6564 20.1405 27.4046 17.8034 21.1853 19.2266L21.0193 19.2682C20.6776 19.3536 20.3877 19.4261 20.1399 19.5171L20.1225 19.5235L20.1048 19.5293C17.6631 20.3263 16.1341 21.8339 15.5155 23.5318C14.8997 25.2222 15.1285 27.2564 16.5089 29.2452C17.2625 30.3308 18.2761 31.2823 19.46 32.0592Z M41.5714 52.075L41.5772 52.087L41.5651 52.0849L41.5714 52.075Z M47.1224 40.0146L42.5493 48.2321C42.4667 46.8997 41.9832 45.5181 41.0622 44.1913C37.9723 39.7398 31.1166 37.5303 25.7414 39.2849L7.16602 45.3854L7.25332 45.1164L12.6666 35.3578L32.3681 28.9194C34.8201 28.119 37.6546 28.2128 40.2542 29.0469C42.8556 29.8816 45.136 31.4297 46.5284 33.4354C48.133 35.7496 48.1942 38.1212 47.1224 40.0146Z M9.07091 46.9944C8.63742 46.7131 8.24774 46.4532 7.90665 46.2212L26.0604 40.2592C28.5122 39.4591 31.3463 39.5529 33.9456 40.387C36.5471 41.2218 38.8277 42.77 40.2201 44.7759C41.6005 46.7647 41.8293 48.7989 41.2135 50.4893C40.5949 52.1872 39.0659 53.6948 36.6242 54.4918L36.6065 54.4976L36.5891 54.504C36.3413 54.595 36.0514 54.6675 35.7097 54.7529L35.5437 54.7946C29.3243 56.2177 22.0725 53.8806 16.231 51.047C13.3356 49.6424 10.8415 48.1431 9.07091 46.9944Z', docs: '/docs/guides/getting-started/quickstarts/solidjs', - gaEvent: 'www_hp_hero_frameworks_solidjs', }, { name: 'Vue', icon: 'M43.0532 13.4531H50.1147L30.2756 47.8158L10.4365 13.4531H17.4978L30.2755 35.5845L43.0532 13.4531ZM42.1764 13.4531L30.2755 34.0659L18.3746 13.4531L25.6939 13.4531L30.2756 21.3888L34.8572 13.4531L42.1764 13.4531Z', docs: '/docs/guides/getting-started/quickstarts/vue', - gaEvent: 'www_hp_hero_frameworks_vue', }, { name: 'Nuxt', icon: 'M32.5784 45.4741H50.2199C50.7802 45.4741 51.3307 45.3325 51.8159 45.0634C52.3012 44.7943 52.7041 44.4072 52.9842 43.9409C53.2642 43.4748 53.4115 42.946 53.4113 42.4078C53.4111 41.8696 53.2633 41.3409 52.9828 40.875L41.1352 21.164C40.8552 20.6979 40.4524 20.3109 39.9672 20.0418C39.4821 19.7727 38.9317 19.631 38.3715 19.631C37.8113 19.631 37.261 19.7727 36.7758 20.0418C36.2906 20.3109 35.8878 20.6979 35.6078 21.164L32.5784 26.2073L26.6555 16.3452C26.3753 15.8792 25.9723 15.4922 25.487 15.2232C25.0017 14.9541 24.4513 14.8125 23.8909 14.8125C23.3306 14.8125 22.7802 14.9541 22.2949 15.2232C21.8096 15.4922 21.4066 15.8792 21.1263 16.3452L6.38358 40.875C6.10311 41.3409 5.95532 41.8696 5.95508 42.4078C5.95483 42.946 6.10214 43.4748 6.38219 43.9409C6.66224 44.4072 7.06515 44.7943 7.5504 45.0634C8.03564 45.3325 8.58612 45.4741 9.14645 45.4741H20.2203C24.6079 45.4741 27.8436 43.6229 30.07 40.0113L38.3706 26.2073L47.0599 40.6619H35.4754L32.5784 45.4741ZM20.0398 40.657L12.3116 40.6553L23.8961 21.3836L29.6763 31.0195L25.8062 37.4599C24.3276 39.8032 22.6479 40.657 20.0398 40.657Z', docs: '/docs/guides/getting-started/quickstarts/nuxtjs', - gaEvent: 'www_hp_hero_frameworks_nuxt', }, { name: 'Refine', icon: 'M31.7374 20.9337C32.7027 20.9337 33.6284 21.3132 34.311 21.9887C34.9936 22.6642 35.377 23.5803 35.377 24.5356V39.4661C35.377 40.4214 34.9936 41.3375 34.311 42.013C33.6284 42.6885 32.7027 43.068 31.7374 43.068C30.7721 43.068 29.8463 42.6885 29.1638 42.013C28.4812 41.3375 28.0978 40.4214 28.0978 39.4661V24.5356C28.0978 24.0626 28.1919 23.5942 28.3748 23.1572C28.5577 22.7203 28.8258 22.3232 29.1638 21.9887C29.5018 21.6543 29.903 21.3889 30.3446 21.2079C30.7861 21.0269 31.2594 20.9337 31.7374 20.9337ZM31.7371 27.1915C33.2665 27.1915 34.5063 25.9646 34.5063 24.451C34.5063 22.9375 33.2665 21.7106 31.7371 21.7106C30.2077 21.7106 28.9679 22.9375 28.9679 24.451C28.9679 25.9646 30.2077 27.1915 31.7371 27.1915Z M54.0424 32C54.0424 44.3777 44.0083 54.4118 31.6306 54.4118C19.2529 54.4118 9.21875 44.3777 9.21875 32C9.21875 19.6223 19.2529 9.58813 31.6306 9.58813C44.0083 9.58813 54.0424 19.6223 54.0424 32ZM31.7374 19.3933C30.36 19.3952 29.0396 19.9376 28.0659 20.9016C27.0921 21.8657 26.5444 23.1726 26.543 24.5356V39.4661C26.543 40.8294 27.0902 42.137 28.0644 43.101C29.0385 44.065 30.3597 44.6066 31.7374 44.6066C33.115 44.6066 34.4362 44.065 35.4104 43.101C36.3845 42.137 36.9318 40.8294 36.9318 39.4661V24.5356C36.9304 23.1726 36.3827 21.8657 35.4089 20.9016C34.4352 19.9376 33.1148 19.3952 31.7374 19.3933Z', docs: '/docs/guides/getting-started/quickstarts/refine', - gaEvent: 'www_hp_hero_frameworks_refine', }, ] @@ -106,21 +95,9 @@ const textVariants = { const HeroFrameworks = ({ className }: { className?: string }) => { const [activeFramework, setActiveFramework] = useState(null!) - const router = useRouter() - const telemetryProps = useTelemetryProps() as any const isXs = useBreakpoint(640) - const sendTelemetryEvent = async (gaEvent: string) => { - return await Telemetry.sendEvent( - { - action: gaEvent, - category: 'link', - label: '', - }, - telemetryProps, - router - ) - } + const sendTelemetryEvent = useSendTelemetryEvent() return ( @@ -148,7 +125,12 @@ const HeroFrameworks = ({ className }: { className?: string }) => { key={framework.name} href={framework.docs} className="transition-opacity group" - onClick={() => sendTelemetryEvent(framework.gaEvent)} + onClick={() => + sendTelemetryEvent({ + action: TelemetryActions.HOMEPAGE_FRAMEWORK_QUICKSTART_CLICKED, + properties: { frameworkName: framework.name }, + }) + } onMouseOver={() => setActiveFramework(framework)} onMouseOut={() => setActiveFramework(null!)} > diff --git a/apps/www/components/Pricing/PricingComparisonTable.tsx b/apps/www/components/Pricing/PricingComparisonTable.tsx index f43380d68b8..22f838126c8 100644 --- a/apps/www/components/Pricing/PricingComparisonTable.tsx +++ b/apps/www/components/Pricing/PricingComparisonTable.tsx @@ -1,16 +1,14 @@ import Link from 'next/link' -import { useRouter } from 'next/router' import { useState } from 'react' -import { useTelemetryProps } from 'common/hooks/useTelemetryProps' import { plans } from 'shared-data/plans' import { pricing } from 'shared-data/pricing' import { Button, Select, cn } from 'ui' import { PricingTableRowDesktop, PricingTableRowMobile } from '~/components/Pricing/PricingTableRow' -import { Organization } from '~/data/organizations' import Solutions from '~/data/MainProducts' -import gaEvents from '~/lib/gaEvents' -import Telemetry, { TelemetryEvent } from '~/lib/telemetry' +import { Organization } from '~/data/organizations' +import { TelemetryActions } from 'common/telemetry-constants' +import { useSendTelemetryEvent } from '~/lib/telemetry' import UpgradePlan from './UpgradePlan' const MobileHeader = ({ @@ -32,11 +30,7 @@ const MobileHeader = ({ organizations?: Organization[] hasExistingOrganizations?: boolean }) => { - const router = useRouter() - const telemetryProps = useTelemetryProps() - const sendTelemetryEvent = async (event: TelemetryEvent) => { - await Telemetry.sendEvent(event, telemetryProps, router) - } + const sendTelemetryEvent = useSendTelemetryEvent() const selectedPlan = plans.find((p) => p.name === plan)! const isUpgradablePlan = selectedPlan.name === 'Pro' || selectedPlan.name === 'Team' @@ -62,9 +56,15 @@ const MobileHeader = ({ - sendTelemetryEvent( - gaEvents[`www_pricing_comparison_${plan.toLowerCase()}_mobile_upgrade`] - ) + sendTelemetryEvent({ + action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + properties: { + plan, + showUpgradeText: true, + section: 'comparison_table', + tableMode: 'mobile', + }, + }) } size="medium" /> @@ -73,7 +73,15 @@ const MobileHeader = ({ - sendTelemetryEvent(gaEvents[`www_pricing_comparison_${plan.toLowerCase()}_mobile`]) + sendTelemetryEvent({ + action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + properties: { + plan, + showUpgradeText: false, + section: 'comparison_table', + tableMode: 'mobile', + }, + }) } > {selectedPlan.cta} @@ -93,13 +101,9 @@ const PricingComparisonTable = ({ organizations, hasExistingOrganizations, }: PricingComparisonTableProps) => { - const router = useRouter() - const telemetryProps = useTelemetryProps() const [activeMobilePlan, setActiveMobilePlan] = useState('Free') - const sendTelemetryEvent = async (event: TelemetryEvent) => { - await Telemetry.sendEvent(event, telemetryProps, router) - } + const sendTelemetryEvent = useSendTelemetryEvent() return (
- sendTelemetryEvent( - gaEvents[ - `www_pricing_comparison_${plan.name.toLowerCase()}_upgrade` - ] - ) + sendTelemetryEvent({ + action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + properties: { + plan: plan.name, + showUpgradeText: true, + section: 'comparison_table', + tableMode: 'desktop', + }, + }) } size="tiny" /> @@ -416,9 +424,15 @@ const PricingComparisonTable = ({ - sendTelemetryEvent( - gaEvents[`www_pricing_comparison_${plan.name.toLowerCase()}`] - ) + sendTelemetryEvent({ + action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + properties: { + plan: plan.name, + showUpgradeText: false, + section: 'comparison_table', + tableMode: 'desktop', + }, + }) } > {plan.cta} diff --git a/apps/www/components/Pricing/PricingPlans.tsx b/apps/www/components/Pricing/PricingPlans.tsx index a27e3c367af..9c3a9b4f259 100644 --- a/apps/www/components/Pricing/PricingPlans.tsx +++ b/apps/www/components/Pricing/PricingPlans.tsx @@ -1,14 +1,12 @@ import Link from 'next/link' -import { useRouter } from 'next/router' -import { useTelemetryProps } from 'common/hooks/useTelemetryProps' +import { Check } from 'lucide-react' import { pickFeatures, pickFooter, plans } from 'shared-data/plans' import { Button, cn } from 'ui' import { Organization } from '~/data/organizations' -import gaEvents from '~/lib/gaEvents' -import Telemetry, { TelemetryEvent } from '~/lib/telemetry' +import { useSendTelemetryEvent } from '~/lib/telemetry' import UpgradePlan from './UpgradePlan' -import { Check } from 'lucide-react' +import { TelemetryActions } from 'common/telemetry-constants' interface PricingPlansProps { organizations?: Organization[] @@ -16,12 +14,7 @@ interface PricingPlansProps { } const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansProps) => { - const router = useRouter() - const telemetryProps = useTelemetryProps() - - const sendTelemetryEvent = async (event: TelemetryEvent) => { - await Telemetry.sendEvent(event, telemetryProps, router) - } + const sendTelemetryEvent = useSendTelemetryEvent() return (
@@ -35,7 +28,14 @@ const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansP const footer = pickFooter(plan) const sendPricingEvent = () => { - sendTelemetryEvent(gaEvents[`www_pricing_hero_plan_${plan.name.toLowerCase()}`]) + sendTelemetryEvent({ + action: TelemetryActions.PRICING_PLAN_CTA_CLICKED, + properties: { + plan: plan.name, + showUpgradeText: isUpgradablePlan && hasExistingOrganizations ? true : false, + section: 'main', + }, + }) } return ( diff --git a/apps/www/components/Products/index.tsx b/apps/www/components/Products/index.tsx index f688e45c050..1df8fb69ff4 100644 --- a/apps/www/components/Products/index.tsx +++ b/apps/www/components/Products/index.tsx @@ -1,7 +1,6 @@ -import { useTelemetryProps } from 'common/hooks/useTelemetryProps' -import { useRouter } from 'next/router' import { Check } from 'lucide-react' +import SectionContainer from '~/components/Layouts/SectionContainer' import AuthVisual from './AuthVisual' import DataAPIsVisual from './DataAPIsVisual' import DatabaseVisual from './DatabaseVisual' @@ -10,11 +9,10 @@ import ProductCard from './ProductCard' import RealtimeVisual from './RealtimeVisual' import StorageVisual from './StorageVisual' import VectorVisual from './VectorVisual' -import SectionContainer from '~/components/Layouts/SectionContainer' import { PRODUCT_MODULES_SHORTNAMES, PRODUCT_SHORTNAMES } from 'shared-data/products' -import gaEvents from '~/lib/gaEvents' -import Telemetry from '~/lib/telemetry' +import { TelemetryActions } from 'common/telemetry-constants' +import { useSendTelemetryEvent } from '~/lib/telemetry' import type { ProductType } from '~/data/MainProducts' @@ -23,56 +21,15 @@ interface Props { } const Products: React.FC = (props) => { - const router = useRouter() - const telemetryProps = useTelemetryProps() + const _sendTelemetryEvent = useSendTelemetryEvent() const sendTelemetryEvent = async ( product: PRODUCT_SHORTNAMES | PRODUCT_MODULES_SHORTNAMES | 'data-api' ) => { - switch (product) { - case PRODUCT_SHORTNAMES.DATABASE: - return await Telemetry.sendEvent( - gaEvents['www_hp_subhero_products_database'], - telemetryProps, - router - ) - case PRODUCT_SHORTNAMES.AUTHENTICATION: - return await Telemetry.sendEvent( - gaEvents['www_hp_subhero_products_auth'], - telemetryProps, - router - ) - case PRODUCT_SHORTNAMES.STORAGE: - return await Telemetry.sendEvent( - gaEvents['www_hp_subhero_products_storage'], - telemetryProps, - router - ) - case PRODUCT_SHORTNAMES.FUNCTIONS: - return await Telemetry.sendEvent( - gaEvents['www_hp_subhero_products_edgeFunctions'], - telemetryProps, - router - ) - case PRODUCT_SHORTNAMES.REALTIME: - return await Telemetry.sendEvent( - gaEvents['www_hp_subhero_products_realtime'], - telemetryProps, - router - ) - case PRODUCT_MODULES_SHORTNAMES.VECTOR: - return await Telemetry.sendEvent( - gaEvents['www_hp_subhero_products_vector'], - telemetryProps, - router - ) - case 'data-api': - return await Telemetry.sendEvent( - gaEvents['www_hp_subhero_products_data-api'], - telemetryProps, - router - ) - } + return await _sendTelemetryEvent({ + action: TelemetryActions.HOMEPAGE_PRODUCT_CARD_CLICKED, + properties: { product }, + }) } return ( diff --git a/apps/www/data/frameworks.ts b/apps/www/data/frameworks.ts index 05607919144..3bf19a52456 100644 --- a/apps/www/data/frameworks.ts +++ b/apps/www/data/frameworks.ts @@ -3,42 +3,35 @@ export const frameworks = { name: 'Next.js', icon: '/images/logos/frameworks/nextjs.svg', docs: '/docs/guides/getting-started/quickstarts/nextjs', - gaEvent: 'www_hp_hero_frameworks_nextjs', }, react: { name: 'React', icon: '/images/logos/frameworks/react.svg', docs: '/docs/guides/getting-started/quickstarts/reactjs', - gaEvent: 'www_hp_hero_frameworks_react', }, nuxt: { name: 'Nuxt', icon: '/images/logos/frameworks/nuxtjs.svg', docs: '/docs/guides/getting-started/quickstarts/nuxtjs', - gaEvent: 'www_hp_hero_frameworks_nuxt', }, flutter: { name: 'Flutter', icon: '/images/logos/frameworks/flutter.svg', docs: '/docs/guides/getting-started/quickstarts/flutter', - gaEvent: 'www_hp_hero_frameworks_flutter', }, svelte: { name: 'Svelte', icon: '/images/logos/frameworks/svelte.svg', docs: '/docs/guides/getting-started/quickstarts/sveltekit', - gaEvent: 'www_hp_hero_frameworks_svelte', }, python: { name: 'Python', icon: '/images/logos/languages/python-icon.svg', docs: '/docs/reference/python/introduction', - gaEvent: 'www_hp_hero_frameworks_python', }, vue: { name: 'Vue', icon: '/images/logos/frameworks/vue.svg', docs: '/docs/guides/getting-started/quickstarts/vue', - gaEvent: 'www_hp_hero_frameworks_vue', }, } diff --git a/apps/www/lib/gaEvents.ts b/apps/www/lib/gaEvents.ts deleted file mode 100644 index a9a2ac7111e..00000000000 --- a/apps/www/lib/gaEvents.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { TelemetryEvent } from './telemetry' - -interface TelemetryEventType { - [key: string]: TelemetryEvent -} - -const TelemetryEvents: TelemetryEventType = { - www_hp_hero_startProject: { - action: 'click', - category: 'www_hp_hero', - label: 'start_project', - }, - www_hp_hero_documentation: { - action: 'click', - category: 'www_hp_hero', - label: 'documentation', - }, - www_hp_subhero_products_database: { - action: 'click', - category: 'www_hp_subhero', - label: 'products_database', - }, - www_hp_subhero_products_auth: { - action: 'click', - category: 'www_hp_subhero', - label: 'products_auth', - }, - www_hp_subhero_products_storage: { - action: 'click', - category: 'www_hp_subhero', - label: 'products_storage', - }, - www_hp_subhero_products_edgeFunctions: { - action: 'click', - category: 'www_hp_subhero', - label: 'products_functions', - }, - www_hp_subhero_products_realtime: { - action: 'click', - category: 'www_hp_subhero', - label: 'products_realtime', - }, - www_hp_subhero_products_vector: { - action: 'click', - category: 'www_hp_subhero', - label: 'products_vector', - }, - www_pricing_hero_plan_free: { - action: 'click', - category: 'www_pricing_hero', - label: 'plan_free', - }, - www_pricing_hero_plan_pro: { - action: 'click', - category: 'www_pricing_hero', - label: 'plan_pro', - }, - www_pricing_hero_plan_team: { - action: 'click', - category: 'www_pricing_hero', - label: 'plan_team', - }, - www_pricing_hero_plan_enterprise: { - action: 'click', - category: 'www_pricing_hero', - label: 'plan_enterprise', - }, - www_pricing_comparison_free: { - action: 'click', - category: 'www_pricing_comparison', - label: 'free', - }, - www_pricing_comparison_pro: { - action: 'click', - category: 'www_pricing_comparison', - label: 'pro', - }, - www_pricing_comparison_team: { - action: 'click', - category: 'www_pricing_comparison', - label: 'team', - }, - www_pricing_comparison_enterprise: { - action: 'click', - category: 'www_pricing_comparison', - label: 'enterprise', - }, - www_event_main_cta: { - action: 'click', - category: 'www_event_page', - label: 'main_cta', - }, -} - -export default TelemetryEvents diff --git a/apps/www/lib/telemetry.ts b/apps/www/lib/telemetry.ts index 6255a45aa4c..fe5f38c7f2a 100644 --- a/apps/www/lib/telemetry.ts +++ b/apps/www/lib/telemetry.ts @@ -1,56 +1,16 @@ -import { LOCAL_STORAGE_KEYS, TelemetryProps } from 'common' -import { API_URL, IS_PREVIEW, IS_PROD } from 'lib/constants' -import { NextRouter } from 'next/router' -import { post } from '~/lib/fetchWrapper' +import { sendTelemetryEvent } from 'common' +import { TelemetryEvent } from 'common/telemetry-constants' +import { API_URL } from 'lib/constants' +import { useRouter } from 'next/router' +import { useCallback } from 'react' -export interface TelemetryEvent { - category: string - action: string - label: string - value?: string -} +export function useSendTelemetryEvent() { + const router = useRouter() -const noop = () => {} - -// This event is the same as in studio/lib/telemetry.tx -// but uses different ENV variables for www - -const sendEvent = (event: TelemetryEvent, telemetryProps: TelemetryProps, router: NextRouter) => { - const consent = - typeof window !== 'undefined' - ? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) - : null - const hasAcceptedConsent = consent === 'true' - const IS_DEV = !IS_PROD && !IS_PREVIEW - const blockEvent = IS_DEV || !hasAcceptedConsent - - if (blockEvent) return noop - - const { category, action, label, value } = event - const title = typeof document !== 'undefined' ? document?.title : '' - const referrer = typeof document !== 'undefined' ? document?.referrer : '' - - const { page_url, search, language, viewport_height, viewport_width } = telemetryProps - - return post( - `${API_URL}/telemetry/event`, - { - page_url, - action: action, - page_title: title, - pathname: router.pathname, - ph: { - search, - referrer, - language, - viewport_height, - viewport_width, - user_agent: navigator.userAgent, - }, - custom_properties: { category, label, value } as any, + return useCallback( + (event: TelemetryEvent) => { + return sendTelemetryEvent(API_URL, event, router.pathname) }, - { headers: { Version: '2' }, credentials: 'include' } + [router.pathname] ) } - -export default { sendEvent } diff --git a/apps/www/pages/events/[slug].tsx b/apps/www/pages/events/[slug].tsx index 3e88da25306..49555085c82 100644 --- a/apps/www/pages/events/[slug].tsx +++ b/apps/www/pages/events/[slug].tsx @@ -1,37 +1,34 @@ -import React from 'react' -import Link from 'next/link' -import NextImage from 'next/image' -import { useRouter } from 'next/router' -import { MDXRemote } from 'next-mdx-remote' -import { NextSeo } from 'next-seo' -import dayjs from 'dayjs' -import matter from 'gray-matter' import { DesktopComputerIcon, - VideoCameraIcon, - MicrophoneIcon, HandIcon, + MicrophoneIcon, + VideoCameraIcon, } from '@heroicons/react/solid' +import dayjs from 'dayjs' +import matter from 'gray-matter' import capitalize from 'lodash/capitalize' import { ChevronLeft, X as XIcon } from 'lucide-react' +import { MDXRemote } from 'next-mdx-remote' +import { NextSeo } from 'next-seo' +import NextImage from 'next/image' +import Link from 'next/link' import authors from 'lib/authors.json' +import { TelemetryActions } from 'common/telemetry-constants' import { isNotNullOrUndefined } from '~/lib/helpers' import mdxComponents from '~/lib/mdx/mdxComponents' import { mdxSerialize } from '~/lib/mdx/mdxSerialize' import { getAllPostSlugs, getPostdata } from '~/lib/posts' -import { isBrowser, useTelemetryProps } from 'common' -import Telemetry, { TelemetryEvent } from '~/lib/telemetry' -import gaEvents from '~/lib/gaEvents' +import { useSendTelemetryEvent } from '~/lib/telemetry' -import utc from 'dayjs/plugin/utc' -import timezone from 'dayjs/plugin/timezone' import advancedFormat from 'dayjs/plugin/advancedFormat' +import timezone from 'dayjs/plugin/timezone' +import utc from 'dayjs/plugin/utc' import { Button, Image } from 'ui' +import ShareArticleActions from '~/components/Blog/ShareArticleActions' import DefaultLayout from '~/components/Layouts/Default' import SectionContainer from '~/components/Layouts/SectionContainer' -import ShareArticleActions from '~/components/Blog/ShareArticleActions' import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png' import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png' @@ -185,11 +182,7 @@ const EventPage = ({ event }: InferGetStaticPropsType) => const Icon = eventIcons[event.type] - const router = useRouter() - const telemetryProps = useTelemetryProps() - const sendTelemetryEvent = async (event: TelemetryEvent) => { - await Telemetry.sendEvent(event, telemetryProps, router) - } + const sendTelemetryEvent = useSendTelemetryEvent() return ( <> @@ -292,7 +285,12 @@ const EventPage = ({ event }: InferGetStaticPropsType) => sendTelemetryEvent(gaEvents['www_event'])} + onClick={() => + sendTelemetryEvent({ + action: TelemetryActions.EVENT_PAGE_CTA_CLICKED, + properties: { eventTitle: event.title }, + }) + } > {IS_REGISTRATION_OPEN ? event.main_cta?.label diff --git a/packages/common/hooks/index.ts b/packages/common/hooks/index.ts index 0dde8496c95..5f30407c91f 100644 --- a/packages/common/hooks/index.ts +++ b/packages/common/hooks/index.ts @@ -7,6 +7,5 @@ export * from './useDragToClose' export * from './useIsomorphicLayoutEffect' export * from './useParams' export * from './useSearchParamsShallow' -export * from './useTelemetryProps' export * from './useTelemetryCookie' export * from './useThemeSandbox' diff --git a/packages/common/hooks/useTelemetryCookie.tsx b/packages/common/hooks/useTelemetryCookie.tsx index e35e485c137..32decc97d17 100644 --- a/packages/common/hooks/useTelemetryCookie.tsx +++ b/packages/common/hooks/useTelemetryCookie.tsx @@ -1,8 +1,8 @@ // hooks/useTelemetryCookie.ts import { useRouter } from 'next/compat/router' import { useEffect } from 'react' -import { LOCAL_STORAGE_KEYS, IS_PROD } from '../constants' -import { useTelemetryProps } from './useTelemetryProps' +import { IS_PROD, LOCAL_STORAGE_KEYS } from '../constants' +import { getSharedTelemetryData } from '../telemetry' interface UseTelemetryCookieProps { hasAcceptedConsent: boolean @@ -20,7 +20,6 @@ export function useTelemetryCookie({ referrer, }: UseTelemetryCookieProps) { const router = useRouter() - const { language, search, viewport_height, viewport_width } = useTelemetryProps() const telemetryStorageKey = LOCAL_STORAGE_KEYS.TELEMETRY_DATA useEffect(() => { @@ -32,36 +31,13 @@ export function useTelemetryCookie({ const telemetryCookie = cookies.find((cookie) => cookie.trim().startsWith(telemetryStorageKey)) if (telemetryCookie) return - const telemetryData = { - page_url: window.location.href, - page_title: title, - pathname: router.pathname, - ph: { - referrer, - language, - search, - viewport_height, - viewport_width, - user_agent: navigator.userAgent, - }, - } + const telemetryData = getSharedTelemetryData(router.pathname) if (!hasAcceptedConsent) { const encodedData = encodeURIComponent(JSON.stringify(telemetryData)) document.cookie = `${telemetryStorageKey}=${encodedData}; ${cookieOptions}` } - }, [ - hasAcceptedConsent, - router?.isReady, - telemetryStorageKey, - title, - router?.pathname, - referrer, - language, - search, - viewport_height, - viewport_width, - ]) + }, [hasAcceptedConsent, router?.isReady, telemetryStorageKey, title, router?.pathname, referrer]) } export default useTelemetryCookie diff --git a/packages/common/hooks/useTelemetryProps.ts b/packages/common/hooks/useTelemetryProps.ts deleted file mode 100644 index 6218986da72..00000000000 --- a/packages/common/hooks/useTelemetryProps.ts +++ /dev/null @@ -1,31 +0,0 @@ -'use client' - -import { useRouter } from 'next/compat/router' -import { usePathname } from 'next/navigation' -import { isBrowser } from '../helpers' - -export interface TelemetryProps { - screenResolution?: string - page_url: string - pathname: string - search: string - language: string - viewport_height: number - viewport_width: number -} - -export function useTelemetryProps(): TelemetryProps { - const router = useRouter() - const pathname = usePathname() - const locale = router ? router.locale : undefined - - return { - screenResolution: isBrowser ? `${window.innerWidth}x${window.innerHeight}` : undefined, - page_url: isBrowser ? window.location.href : '', - pathname: router?.pathname ?? pathname ?? (isBrowser ? window.location.pathname : ''), - search: isBrowser ? window.location.search : '', - viewport_height: isBrowser ? window.innerHeight : 0, - viewport_width: isBrowser ? window.innerWidth : 0, - language: locale ?? 'en-US', - } -} diff --git a/apps/studio/lib/constants/telemetry.ts b/packages/common/telemetry-constants.ts similarity index 76% rename from apps/studio/lib/constants/telemetry.ts rename to packages/common/telemetry-constants.ts index 7d91b1f0138..f31610ee0e7 100644 --- a/apps/studio/lib/constants/telemetry.ts +++ b/packages/common/telemetry-constants.ts @@ -41,6 +41,16 @@ export enum TelemetryActions { SQL_EDITOR_RESULT_DOWNLOAD_CSV_CLICKED = 'sql_editor_result_download_csv_clicked', SQL_EDITOR_RESULT_COPY_MARKDOWN_CLICKED = 'sql_editor_result_copy_markdown_clicked', SQL_EDITOR_RESULT_COPY_JSON_CLICKED = 'sql_editor_result_copy_markdown_clicked', + + DOCS_FEEDBACK_CLICKED = 'docs_feedback_clicked', + + HOMEPAGE_HERO_START_PROJECT_CLICKED = 'homepage_hero_start_project_clicked', + HOMEPAGE_HERO_REQUEST_DEMO_CLICKED = 'homepage_hero_request_demo_clicked', + HOMEPAGE_FRAMEWORK_QUICKSTART_CLICKED = 'homepage_framework_quickstart_clicked', + HOMEPAGE_PRODUCT_CARD_CLICKED = 'homepage_product_card_clicked', + PRICING_PLAN_CTA_CLICKED = 'pricing_plan_cta_clicked', + PRICING_COMPARISON_PLAN_CLICKED = 'pricing_comparison_plan_clicked', + EVENT_PAGE_CTA_CLICKED = 'event_page_cta_clicked', } /** @@ -616,3 +626,173 @@ export interface AssistantEditInSqlEditorClickedEvent { organization: string } } + +/** + * User voted on the feedback button on a docs page. The feedback button is located at the sidebar of every docs page. + * + * @group Events + * @source docs + */ +export interface DocsFeedbackClickedEvent { + action: TelemetryActions.DOCS_FEEDBACK_CLICKED + properties: { + /** + * 'yes' means clicking on the tick button, 'no' means clicking on the cross button. + */ + response: 'yes' | 'no' + } +} + +/** + * User clicked the "Start Project" button in the homepage hero section. + * + * @group Events + * @source www + * @page / + */ +export interface HomepageHeroStartProjectClickedEvent { + action: TelemetryActions.HOMEPAGE_HERO_START_PROJECT_CLICKED +} + +/** + * User clicked the "Request a Demo" button in the homepage hero section. + * + * @group Events + * @source www + * @page / + */ +export interface HomepageHeroRequestDemoClickedEvent { + action: TelemetryActions.HOMEPAGE_HERO_REQUEST_DEMO_CLICKED +} + +/** + * Framework quickstart card clicked in homepage and will lead to specific framework doc. + * + * @group Events + * @source www + * @page / + */ +export interface HomepageFrameworkQuickstartClickedEvent { + action: TelemetryActions.HOMEPAGE_FRAMEWORK_QUICKSTART_CLICKED + properties: { + /** + * The name of the framework quickstart card clicked. + */ + frameworkName: string + } +} + +/** + * User clicked on a product card in the homepage products section. + * + * @group Events + * @source www + * @page / + */ +export interface HomepageProductCardClickedEvent { + action: TelemetryActions.HOMEPAGE_PRODUCT_CARD_CLICKED + properties: { + product: string + } +} + +/** + * User clicked on the green button on a plan in the pricing page. + * + * @group Events + * @source www + * @page /pricing + */ +export interface PricingPlanCtaClickedEvent { + action: TelemetryActions.PRICING_PLAN_CTA_CLICKED + properties: { + /** + * The plan type that was clicked. + */ + plan: string + /** + * Whether the upgrade now text is shown on the cta button. This is only shown when org is upgradeable and user is logged in. + */ + showUpgradeText: boolean + /** + * The section of the page where the plan was clicked. Main means the big top section of the page, comparison_table means the comparison table with all plans listen together below. + */ + section: 'main' | 'comparison_table' + tableMode?: 'mobile' | 'desktop' + } +} + +/** + * User clicked on a plan in the pricing comparison section. + * + * @group Events + * @source www + * @page /pricing + */ +export interface PricingComparisonPlanClickedEvent { + action: TelemetryActions.PRICING_COMPARISON_PLAN_CLICKED + properties: { + category: 'pricing_comparison' + /** + * The plan type that was clicked. + */ + plan: 'free' | 'pro' | 'team' | 'enterprise' + } +} + +/** + * User clicked on the main CTA button in an event page. + * + * @group Events + * @source www + * @page /events/* + */ +export interface EventPageCtaClickedEvent { + action: TelemetryActions.EVENT_PAGE_CTA_CLICKED + properties: { + eventTitle: string + } +} + +export type TelemetryEvent = + | SignUpEvent + | SignInEvent + | ConnectionStringCopiedEvent + | CronJobCreatedEvent + | CronJobUpdatedEvent + | CronJobDeletedEvent + | CronJobCreateClickedEvent + | CronJobUpdateClickedEvent + | CronJobDeleteClickedEvent + | CronJobHistoryClickedEvent + | FeaturePreviewsClickedEvent + | FeaturePreviewEnabledEvent + | FeaturePreviewDisabledEvent + | ProjectCreationInitialStepPromptIntendedEvent + | ProjectCreationInitialStepSubmittedEvent + | ProjectCreationSecondStepPromptIntendedEvent + | ProjectCreationSecondStepSubmittedEvent + | RealtimeInspectorListenChannelClickedEvent + | RealtimeInspectorBroadcastSentEvent + | RealtimeInspectorMessageClickedEvent + | RealtimeInspectorCopyMessageClickedEvent + | RealtimeInspectorFiltersAppliedEvent + | RealtimeInspectorDatabaseRoleUpdatedEvent + | SqlEditorQuickstartClickedEvent + | SqlEditorTemplateClickedEvent + | SqlEditorResultDownloadCsvClickedEvent + | SqlEditorResultCopyMarkdownClickedEvent + | SqlEditorResultCopyJsonClickedEvent + | AssistantPromptSubmittedEvent + | AssistantDebugSubmittedEvent + | AssistantSuggestionRunQueryClickedEvent + | AssistantSqlDiffHandlerEvaluatedEvent + | AssistantEditInSqlEditorClickedEvent + | DocsFeedbackClickedEvent + | HomepageHeroStartProjectClickedEvent + | HomepageHeroRequestDemoClickedEvent + | HomepageFrameworkQuickstartClickedEvent + | HomepageProductCardClickedEvent + | PricingPlanCtaClickedEvent + | PricingComparisonPlanClickedEvent + | EventPageCtaClickedEvent diff --git a/packages/common/telemetry.tsx b/packages/common/telemetry.tsx index 78b6ccc7964..b4ae2d64e42 100644 --- a/packages/common/telemetry.tsx +++ b/packages/common/telemetry.tsx @@ -2,10 +2,17 @@ import { components } from 'api-types' import { useRouter } from 'next/compat/router' import { usePathname } from 'next/navigation' import { useCallback, useEffect, useRef } from 'react' +import { LOCAL_STORAGE_KEYS } from './constants' import { useFeatureFlags } from './feature-flags' import { post } from './fetchWrappers' import { ensurePlatformSuffix, isBrowser } from './helpers' import { useTelemetryCookie } from './hooks' +import { TelemetryEvent } from './telemetry-constants' +import { useUser } from './auth' + +//--- +// PAGE TELEMETRY +//--- export function handlePageTelemetry( API_URL: string, @@ -20,17 +27,7 @@ export function handlePageTelemetry( telemetryDataOverride !== undefined ? { feature_flags: featureFlags, ...telemetryDataOverride } : { - page_url: isBrowser ? window.location.href : '', - page_title: isBrowser ? document?.title : '', - pathname: pathname ? pathname : isBrowser ? window.location.pathname : '', - ph: { - referrer: isBrowser ? document?.referrer : '', - language: navigator.language ?? 'en-US', - user_agent: navigator.userAgent, - search: isBrowser ? window.location.search : '', - viewport_height: isBrowser ? window.innerHeight : 0, - viewport_width: isBrowser ? window.innerWidth : 0, - }, + ...getSharedTelemetryData(pathname), feature_flags: featureFlags, }, { headers: { Version: '2' } } @@ -54,10 +51,6 @@ export function handlePageLeaveTelemetry( }) } -export function handleResetTelemetry(API_URL: string) { - return post(`${API_URL}/telemetry/reset`, {}) -} - export const PageTelemetry = ({ API_URL, hasAcceptedConsent, @@ -152,5 +145,93 @@ export const PageTelemetry = ({ return () => window.removeEventListener('beforeunload', handleBeforeUnload) }, [enabled, sendPageLeaveTelemetry]) + // Identify the user + useTelemetryIdentify(API_URL) + return null } + +// --- +// EVENT TELEMETRY +// --- + +type EventBody = components['schemas']['TelemetryEventBodyV2Dto'] + +export function sendTelemetryEvent(API_URL: string, event: TelemetryEvent, pathname?: string) { + const consent = + (typeof window !== 'undefined' + ? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) + : null) === 'true' + + if (!consent) return + + const body: EventBody = { + ...getSharedTelemetryData(pathname), + action: event.action, + custom_properties: 'properties' in event ? event.properties : {}, + groups: 'groups' in event ? event.groups : {}, + } + + return post(`${ensurePlatformSuffix(API_URL)}/telemetry/event`, body, { + headers: { Version: '2' }, + }) +} + +//--- +// TELEMETRY IDENTIFY +//--- + +type IdentifyBody = components['schemas']['TelemetryIdentifyBodyV2'] + +export function sendTelemetryIdentify(API_URL: string, body: IdentifyBody) { + const consent = + (typeof window !== 'undefined' + ? localStorage.getItem(LOCAL_STORAGE_KEYS.TELEMETRY_CONSENT) + : null) === 'true' + + if (!consent) return Promise.resolve() + + return post(`${ensurePlatformSuffix(API_URL)}/telemetry/identify`, body, { + headers: { Version: '2' }, + }) +} + +export function useTelemetryIdentify(API_URL: string) { + const user = useUser() + + useEffect(() => { + if (user?.id) { + sendTelemetryIdentify(API_URL, { + user_id: user.id, + }) + } + }, [API_URL, user?.id]) +} + +//--- +// TELEMETRY RESET +//--- + +export function handleResetTelemetry(API_URL: string) { + return post(`${API_URL}/telemetry/reset`, {}) +} + +// --- +// TELEMETRY UTILS +// --- + +export function getSharedTelemetryData(pathname?: string) { + return { + page_url: isBrowser ? window.location.href : '', + page_title: isBrowser ? document?.title : '', + pathname: pathname ? pathname : isBrowser ? window.location.pathname : '', + ph: { + referrer: isBrowser ? document?.referrer : '', + language: navigator.language ?? 'en-US', + user_agent: navigator.userAgent, + search: isBrowser ? window.location.search : '', + viewport_height: isBrowser ? window.innerHeight : 0, + viewport_width: isBrowser ? window.innerWidth : 0, + }, + } +} diff --git a/packages/ui-patterns/ConsentToast/index.tsx b/packages/ui-patterns/ConsentToast/index.tsx index d407f95686d..e5c80c8929e 100644 --- a/packages/ui-patterns/ConsentToast/index.tsx +++ b/packages/ui-patterns/ConsentToast/index.tsx @@ -6,7 +6,6 @@ import { isBrowser, useBreakpoint, useFeatureFlags, - useTelemetryProps, } from 'common' import { noop } from 'lodash' import { useCallback, useEffect, useRef, useState } from 'react' @@ -14,6 +13,8 @@ import { toast } from 'sonner' import { Button, cn } from 'ui' import { proxy, useSnapshot } from 'valtio' import { PrivacySettings } from '../PrivacySettings' +import { useRouter } from 'next/compat/router' +import { usePathname } from 'next/navigation' interface ConsentToastProps { onAccept: () => void @@ -91,10 +92,13 @@ const consentState = proxy({ export const useConsent = () => { const { TELEMETRY_CONSENT, TELEMETRY_DATA } = LOCAL_STORAGE_KEYS const consentToastId = useRef() - const telemetryProps = useTelemetryProps() + const router = useRouter() + const appRouterPathname = usePathname() const featureFlags = useFeatureFlags() const { consentValue } = useSnapshot(consentState) + const pathname = + router?.pathname ?? appRouterPathname ?? (isBrowser ? window.location.pathname : '') const handleConsent = (value: 'true' | 'false') => { if (!isBrowser) return @@ -108,7 +112,7 @@ export const useConsent = () => { const telemetryData = JSON.parse(decodeURIComponent(encodedData)) handlePageTelemetry( process.env.NEXT_PUBLIC_API_URL!, - telemetryProps.pathname, + pathname, featureFlags.posthog, telemetryData ) @@ -118,11 +122,7 @@ export const useConsent = () => { console.error('Invalid telemetry data:', error) } } else { - handlePageTelemetry( - process.env.NEXT_PUBLIC_API_URL!, - telemetryProps.pathname, - featureFlags.posthog - ) + handlePageTelemetry(process.env.NEXT_PUBLIC_API_URL!, pathname, featureFlags.posthog) } } else { // remove the telemetry cookie @@ -184,7 +184,6 @@ export const useConsent = () => { } export const useConsentValue = (KEY_NAME: string) => { - const telemetryProps = useTelemetryProps() const initialValue = isBrowser ? localStorage?.getItem(KEY_NAME) : null const [consentValue, setConsentValue] = useState(initialValue) @@ -194,21 +193,7 @@ export const useConsentValue = (KEY_NAME: string) => { localStorage.setItem(KEY_NAME, value) window.dispatchEvent(new Event('storage')) if (value === 'true') { - const telemetryData = { - page_url: telemetryProps.page_url, - page_title: typeof document !== 'undefined' ? document?.title : '', - pathname: telemetryProps.pathname, - ph: { - referrer: typeof document !== 'undefined' ? document?.referrer : '', - language: telemetryProps.language, - search: telemetryProps.search, - viewport_height: telemetryProps.viewport_height, - viewport_width: telemetryProps.viewport_width, - user_agent: navigator.userAgent, - }, - } - - handlePageTelemetry(process.env.NEXT_PUBLIC_API_URL!, location.pathname, telemetryData) + handlePageTelemetry(process.env.NEXT_PUBLIC_API_URL!, location.pathname) } }