Skip to content

Commit 6e38615

Browse files
committed
Add automatic page tracking.
1 parent d0085e2 commit 6e38615

File tree

5 files changed

+114
-70
lines changed

5 files changed

+114
-70
lines changed

apps/reactotron-app/src/renderer/App.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import Overlay from "./pages/reactNative/Overlay"
1515
import Storybook from "./pages/reactNative/Storybook"
1616
import CustomCommands from "./pages/customCommands"
1717
import Help from "./pages/help"
18+
import { usePageTracking } from "./util/analyticsHelpers"
1819

19-
const AppContainer = styled.div`
20+
const AppContainerComponent = styled.div`
2021
position: absolute;
2122
top: 0;
2223
bottom: 0;
@@ -27,6 +28,10 @@ const AppContainer = styled.div`
2728
flex-direction: column;
2829
background-color: ${(props) => props.theme.background};
2930
`
31+
const AppContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => {
32+
usePageTracking()
33+
return <AppContainerComponent>{children}</AppContainerComponent>
34+
}
3035

3136
const TopSection = styled.div`
3237
overflow: hidden;

apps/reactotron-app/src/renderer/KeybindHandler.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,34 @@ function KeybindHandler({ children }) {
9595
const { openDispatchModal, openSubscriptionModal, clearCommands } = useContext(ReactotronContext)
9696
const { openSearch, toggleSearch } = useContext(TimelineContext)
9797
const { createSnapshot } = useContext(StateContext)
98-
const { sendKeyboardShortcut } = useAnalytics()
98+
const { sendKeyboardShortcutAnalyticsEvent } = useAnalytics()
9999

100100
const handlers = {
101101
// Tab Navigation
102102
OpenHomeTab: () => {
103103
window.location.hash = "/"
104104

105-
sendKeyboardShortcut("OpenHomeTab")
105+
sendKeyboardShortcutAnalyticsEvent("OpenHomeTab")
106106
},
107107
OpenTimelineTab: () => {
108108
window.location.hash = "/timeline"
109-
sendKeyboardShortcut("OpenTimelineTab")
109+
sendKeyboardShortcutAnalyticsEvent("OpenTimelineTab")
110110
},
111111
OpenStateTab: () => {
112112
window.location.hash = "/state/subscriptions"
113-
sendKeyboardShortcut("OpenStateTab")
113+
sendKeyboardShortcutAnalyticsEvent("OpenStateTab")
114114
},
115115
OpenReactNativeTab: () => {
116116
window.location.hash = "/native/overlay"
117-
sendKeyboardShortcut("OpenReactNativeTab")
117+
sendKeyboardShortcutAnalyticsEvent("OpenReactNativeTab")
118118
},
119119
OpenCustomCommandsTab: () => {
120120
window.location.hash = "/customCommands"
121-
sendKeyboardShortcut("OpenCustomCommandsTab")
121+
sendKeyboardShortcutAnalyticsEvent("OpenCustomCommandsTab")
122122
},
123123
OpenHelpTab: () => {
124124
window.location.hash = "/help"
125-
sendKeyboardShortcut("OpenHelpTab")
125+
sendKeyboardShortcutAnalyticsEvent("OpenHelpTab")
126126
},
127127

128128
// Modals
@@ -142,7 +142,7 @@ function KeybindHandler({ children }) {
142142
// Miscellaneous
143143
ToggleSidebar: () => {
144144
toggleSideBar()
145-
sendKeyboardShortcut("ToggleSidebar")
145+
sendKeyboardShortcutAnalyticsEvent("ToggleSidebar")
146146
},
147147
ToggleSearch: () => {
148148
// If we're on the timeline page, toggle the search, otherwise switch to the timeline tab and open search
@@ -155,7 +155,7 @@ function KeybindHandler({ children }) {
155155
},
156156
ClearTimeline: () => {
157157
clearCommands()
158-
sendKeyboardShortcut("ClearTimeline")
158+
sendKeyboardShortcutAnalyticsEvent("ClearTimeline")
159159
},
160160
}
161161

apps/reactotron-app/src/renderer/pages/customCommands/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import styled from "styled-components"
55
import { MdSearch } from "react-icons/md"
66
import { FaMagic } from "react-icons/fa"
77
import { produce } from "immer"
8+
import { useAnalytics } from "../../util/analyticsHelpers"
89

910
const Container = styled.div`
1011
display: flex;
@@ -113,6 +114,7 @@ function CustomCommandItem({
113114
customCommand: CustomCommand
114115
sendCustomCommand: (command: any, args: any) => void
115116
}) {
117+
const { sendCustomCommandAnalyticsEvent } = useAnalytics()
116118
const [state, dispatch] = useReducer(customCommandItemReducer, customCommand.args, (args) => {
117119
if (!args) return {}
118120

@@ -157,6 +159,7 @@ function CustomCommandItem({
157159
<SendButton
158160
onClick={() => {
159161
sendCustomCommand(customCommand.command, state)
162+
sendCustomCommandAnalyticsEvent(customCommand.command, customCommand.title || "")
160163
}}
161164
>
162165
Send Command
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { useCallback, useEffect, useState } from "react"
2+
import ReactGA from "react-ga4"
3+
import packageJson from "../../../package.json"
4+
import { useLocation } from "react-router-dom"
5+
6+
const GA4_KEY = "G-WZE3E5XCQ7"
7+
8+
type UaEventOptions = {
9+
action: string
10+
category: string
11+
label?: string
12+
value?: number
13+
nonInteraction?: boolean
14+
transport?: "beacon" | "xhr" | "image"
15+
}
16+
const isDevelopment = process.env.NODE_ENV !== "production"
17+
18+
export const useAnalytics = () => {
19+
const [initialized, setInitialized] = useState(false)
20+
// const [optedOut, setOptedOut] = useState(false) // Setting this to true will disable analytics
21+
const [optedOut, setOptedOut] = useState(isDevelopment)
22+
23+
useEffect(() => {
24+
const initialize = () => {
25+
const testMode = process.env.NODE_ENV === "test" // we don't want to send analytics events during tests
26+
ReactGA.initialize(GA4_KEY, {testMode: testMode || optedOut})
27+
ReactGA.set({
28+
app_version: packageJson.version,
29+
app_platform: process.platform,
30+
app_arch: process.arch,
31+
})
32+
}
33+
34+
if (!initialized) {
35+
initialize()
36+
setInitialized(true)
37+
}
38+
}, [initialized, optedOut])
39+
40+
const sendAnalyticsEvent = useCallback(
41+
(event: UaEventOptions) => {
42+
console.log("Sending analytics event", event)
43+
ReactGA.event(event)
44+
},
45+
[]
46+
)
47+
48+
const sendPageViewAnalyticsEvent = useCallback(
49+
(page: string, title: string) => {
50+
console.log("Sending page view analytics event", {page, title})
51+
ReactGA.send({ hitType: "pageview", page, title })
52+
},
53+
[]
54+
)
55+
56+
const sendKeyboardShortcutAnalyticsEvent = useCallback(
57+
(label: string) => {
58+
sendAnalyticsEvent({
59+
category: "navigation",
60+
action: "keyboard_shortcut",
61+
nonInteraction: false,
62+
label,
63+
})
64+
},
65+
[sendAnalyticsEvent]
66+
)
67+
68+
const sendCustomCommandAnalyticsEvent = useCallback(
69+
(command: string, title: string) => {
70+
sendAnalyticsEvent({
71+
category: "custom_command",
72+
action: command,
73+
nonInteraction: false,
74+
label: title,
75+
})
76+
},
77+
[sendAnalyticsEvent]
78+
)
79+
80+
return {
81+
sendAnalyticsEvent,
82+
sendPageViewAnalyticsEvent,
83+
sendKeyboardShortcutAnalyticsEvent,
84+
sendCustomCommandAnalyticsEvent,
85+
}
86+
}
87+
88+
// This hook is used to track page views within the app automatically using react-router-dom
89+
export const usePageTracking = () => {
90+
const location = useLocation()
91+
const { sendPageViewAnalyticsEvent } = useAnalytics()
92+
93+
useEffect(() => {
94+
sendPageViewAnalyticsEvent(location.pathname, location.key)
95+
}, [location, sendPageViewAnalyticsEvent])
96+
}

apps/reactotron-app/src/renderer/util/analyticsHelpers.tsx

Lines changed: 0 additions & 60 deletions
This file was deleted.

0 commit comments

Comments
 (0)