diff --git a/apps/dashboard/src/api/telemetry.ts b/apps/dashboard/src/api/telemetry.ts new file mode 100644 index 00000000000..3c9ff38eb0b --- /dev/null +++ b/apps/dashboard/src/api/telemetry.ts @@ -0,0 +1,8 @@ +import { post } from './api.client'; + +export const sendTelemetry = async (event: string, data?: Record): Promise => { + await post('/telemetry/measure', { + event, + data, + }); +}; diff --git a/apps/dashboard/src/components/primitives/dialog.tsx b/apps/dashboard/src/components/primitives/dialog.tsx index 89a615b43f2..686fcd90c6b 100644 --- a/apps/dashboard/src/components/primitives/dialog.tsx +++ b/apps/dashboard/src/components/primitives/dialog.tsx @@ -58,7 +58,7 @@ const DialogHeader = ({ className, ...props }: React.HTMLAttributes, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); DialogDescription.displayName = DialogPrimitive.Description.displayName; diff --git a/apps/dashboard/src/components/side-navigation/side-navigation.tsx b/apps/dashboard/src/components/side-navigation/side-navigation.tsx index 76806e7bd00..ad0b3643610 100644 --- a/apps/dashboard/src/components/side-navigation/side-navigation.tsx +++ b/apps/dashboard/src/components/side-navigation/side-navigation.tsx @@ -18,6 +18,8 @@ import { OrganizationDropdown } from './organization-dropdown'; import { FreeTrialCard } from './free-trial-card'; import { buildRoute, LEGACY_ROUTES, ROUTES } from '@/utils/routes'; import { SubscribersStayTunedModal } from './subscribers-stay-tuned-modal'; +import { useTelemetry } from '@/hooks'; +import { TelemetryEvent } from '@/utils/telemetry'; const linkVariants = cva( `flex items-center gap-2 text-sm py-1.5 px-2 rounded-lg focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer`, @@ -82,6 +84,7 @@ const NavigationGroup = ({ children, label }: { children: ReactNode; label?: str export const SideNavigation = () => { const { currentEnvironment, environments, switchEnvironment } = useEnvironment(); + const track = useTelemetry(); const environmentNames = useMemo(() => environments?.map((env) => env.name), [environments]); const onEnvironmentChange = (value: string) => { const environment = environments?.find((env) => env.name === value); @@ -100,7 +103,7 @@ export const SideNavigation = () => { Workflows - + track(TelemetryEvent.SUBSCRIBERS_LINK_CLICKED)}> Subscribers diff --git a/apps/dashboard/src/components/side-navigation/subscribers-stay-tuned-modal.tsx b/apps/dashboard/src/components/side-navigation/subscribers-stay-tuned-modal.tsx index aafa40b2709..c60234ab67c 100644 --- a/apps/dashboard/src/components/side-navigation/subscribers-stay-tuned-modal.tsx +++ b/apps/dashboard/src/components/side-navigation/subscribers-stay-tuned-modal.tsx @@ -14,7 +14,7 @@ export const SubscribersStayTunedModal = ({ children }: { children: ReactNode })

-

New subscribers page is coming!

+

New subscribers page is coming!

In the meantime, you can keep using Novu’s powerful APIs to access your subscribers.

diff --git a/apps/dashboard/src/config/index.ts b/apps/dashboard/src/config/index.ts index a8ce1e6c792..d112dfcb54b 100644 --- a/apps/dashboard/src/config/index.ts +++ b/apps/dashboard/src/config/index.ts @@ -15,3 +15,7 @@ if (!CLERK_PUBLISHABLE_KEY) { } export const API_HOSTNAME = import.meta.env.VITE_API_HOSTNAME; + +export const SEGMENT_KEY = import.meta.env.VITE_SEGMENT_KEY; + +export const MIXPANEL_KEY = import.meta.env.VITE_MIXPANEL_KEY; diff --git a/apps/dashboard/src/hooks/index.ts b/apps/dashboard/src/hooks/index.ts index 8f26cd8e3ae..e8e4ba07f93 100644 --- a/apps/dashboard/src/hooks/index.ts +++ b/apps/dashboard/src/hooks/index.ts @@ -1 +1,2 @@ export * from './use-billing-subscription'; +export * from './use-telemetry'; diff --git a/apps/dashboard/src/hooks/use-telemetry.ts b/apps/dashboard/src/hooks/use-telemetry.ts new file mode 100644 index 00000000000..d3e23c32755 --- /dev/null +++ b/apps/dashboard/src/hooks/use-telemetry.ts @@ -0,0 +1,31 @@ +import { useCallback } from 'react'; +import { useMutation } from '@tanstack/react-query'; +import * as mixpanel from 'mixpanel-browser'; +import { sendTelemetry } from '@/api/telemetry'; +import { MIXPANEL_KEY } from '@/config'; +import { TelemetryEvent } from '@/utils/telemetry'; + +export const useTelemetry = () => { + const { mutate } = useMutation }>({ + mutationFn: ({ event, data }) => sendTelemetry(event, data), + }); + + return useCallback( + (event: TelemetryEvent, data?: Record) => { + const mixpanelEnabled = !!MIXPANEL_KEY; + + if (mixpanelEnabled) { + // @ts-expect-error missing from types + const sessionReplayProperties = mixpanel.get_session_recording_properties(); + + data = { + ...(data || {}), + ...sessionReplayProperties, + }; + } + + mutate({ event: `${event} - [DASHBOARD]`, data }); + }, + [mutate] + ); +}; diff --git a/apps/dashboard/src/utils/segment.ts b/apps/dashboard/src/utils/segment.ts index 8ba10bcde0c..26a104193f1 100644 --- a/apps/dashboard/src/utils/segment.ts +++ b/apps/dashboard/src/utils/segment.ts @@ -1,6 +1,7 @@ import { AnalyticsBrowser } from '@segment/analytics-next'; import type { IUserEntity } from '@novu/shared'; import * as mixpanel from 'mixpanel-browser'; +import { MIXPANEL_KEY, SEGMENT_KEY } from '@/config'; export class SegmentService { private _segment: AnalyticsBrowser | null = null; @@ -8,11 +9,11 @@ export class SegmentService { public _mixpanelEnabled: boolean; constructor() { - this._segmentEnabled = !!import.meta.env.VITE_SEGMENT_KEY; - this._mixpanelEnabled = !!import.meta.env.VITE_MIXPANEL_KEY; + this._segmentEnabled = !!SEGMENT_KEY; + this._mixpanelEnabled = !!MIXPANEL_KEY; if (this._mixpanelEnabled) { - mixpanel.init(import.meta.env.VITE_MIXPANEL_KEY as string, { + mixpanel.init(MIXPANEL_KEY as string, { //@ts-expect-error missing from types record_sessions_percent: 100, }); @@ -20,7 +21,7 @@ export class SegmentService { if (this._segmentEnabled) { this._segment = AnalyticsBrowser.load({ - writeKey: import.meta.env.VITE_SEGMENT_KEY as string, + writeKey: SEGMENT_KEY as string, }); if (!this._mixpanelEnabled) { return; diff --git a/apps/dashboard/src/utils/telemetry.ts b/apps/dashboard/src/utils/telemetry.ts new file mode 100644 index 00000000000..98ffb208012 --- /dev/null +++ b/apps/dashboard/src/utils/telemetry.ts @@ -0,0 +1,3 @@ +export enum TelemetryEvent { + SUBSCRIBERS_LINK_CLICKED = 'Subscribers link clicked - [Left navigation]', +}