Skip to content

Commit 8bd6d19

Browse files
feat(EventBuilder): enable sending device information (#2245)
* allow screen_view and ad_impression events * allow geo information fields * add support for geo fields * update ip_override description * revert formatting changes * revert ad_impression and screen_view changes * reorder form fields * initial changes for device fields * simplify implementation * continue simplify implementation, cleanup files * add back labels for geo info * additional file cleanup * cleanup form descriptions and labels * add user_agent when client is gtag * revert changes for useTextBox persistence fix, moreved to separate PR * add user_agent field to payload * add ip_override and user_location to test * add device and user_agent fields to test * remove unnecessary else * fix merge conflict update * resolve conflict issues * update helper text * update device description
1 parent 57277fc commit 8bd6d19

File tree

7 files changed

+358
-4
lines changed

7 files changed

+358
-4
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import React, { useContext } from "react"
2+
import Chip from "@mui/material/Chip"
3+
import Divider from "@mui/material/Divider"
4+
import { styled } from "@mui/material/styles"
5+
import Typography from "@mui/material/Typography"
6+
import TextField from "@mui/material/TextField"
7+
import Grid from "@mui/material/Grid"
8+
9+
import ExternalLink from "@/components/ExternalLink"
10+
import { Label } from "./types"
11+
import { UseFirebaseCtx } from "."
12+
13+
const Root = styled("div")(({ theme }) => ({
14+
marginTop: theme.spacing(1),
15+
}))
16+
17+
type DeviceInformationProps = {
18+
device_category: string | undefined
19+
setDeviceCategory: (value: string) => void
20+
device_language: string | undefined
21+
setDeviceLanguage: (value: string) => void
22+
device_screen_resolution: string | undefined
23+
setDeviceScreenResolution: (value: string) => void
24+
device_operating_system: string | undefined
25+
setDeviceOperatingSystem: (value: string) => void
26+
device_operating_system_version: string | undefined
27+
setDeviceOperatingSystemVersion: (value: string) => void
28+
device_model: string | undefined
29+
setDeviceModel: (value: string) => void
30+
device_brand: string | undefined
31+
setDeviceBrand: (value: string) => void
32+
device_browser: string | undefined
33+
setDeviceBrowser: (value: string) => void
34+
device_browser_version: string | undefined
35+
setDeviceBrowserVersion: (value: string) => void
36+
user_agent: string | undefined
37+
setUserAgent: (value: string) => void
38+
}
39+
40+
const DeviceInformation: React.FC<DeviceInformationProps> = ({
41+
device_category,
42+
setDeviceCategory,
43+
device_language,
44+
setDeviceLanguage,
45+
device_screen_resolution,
46+
setDeviceScreenResolution,
47+
device_operating_system,
48+
setDeviceOperatingSystem,
49+
device_operating_system_version,
50+
setDeviceOperatingSystemVersion,
51+
device_model,
52+
setDeviceModel,
53+
device_brand,
54+
setDeviceBrand,
55+
device_browser,
56+
setDeviceBrowser,
57+
device_browser_version,
58+
setDeviceBrowserVersion,
59+
user_agent,
60+
setUserAgent,
61+
}) => {
62+
const useFirebase = useContext(UseFirebaseCtx)
63+
const docHref =
64+
"https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference#device"
65+
return (
66+
<Root>
67+
<Divider>
68+
<Chip label="DEVICE INFORMATION" size="small" />
69+
</Divider>
70+
<Typography variant="h6">Device Attributes</Typography>
71+
<Typography>
72+
See the{" "}
73+
<ExternalLink href={docHref}>documentation</ExternalLink> for more
74+
information about device attributes.
75+
</Typography>
76+
<Grid container spacing={1}>
77+
<Grid item xs={12} sm={6}>
78+
<TextField
79+
fullWidth
80+
id="device-category"
81+
label={Label.DeviceCategory}
82+
variant="outlined"
83+
size="small"
84+
value={device_category || ""}
85+
onChange={e => setDeviceCategory(e.target.value)}
86+
helperText="The category of the device, e.g., mobile, desktop"
87+
/>
88+
</Grid>
89+
<Grid item xs={12} sm={6}>
90+
<TextField
91+
fullWidth
92+
id="device-language"
93+
label={Label.DeviceLanguage}
94+
variant="outlined"
95+
size="small"
96+
value={device_language || ""}
97+
onChange={e => setDeviceLanguage(e.target.value)}
98+
helperText="The language of the device in ISO 639-1 format, e.g., en"
99+
/>
100+
</Grid>
101+
<Grid item xs={12} sm={6}>
102+
<TextField
103+
fullWidth
104+
id="device-screen-resolution"
105+
label={Label.DeviceScreenResolution}
106+
variant="outlined"
107+
size="small"
108+
value={device_screen_resolution || ""}
109+
onChange={e => setDeviceScreenResolution(e.target.value)}
110+
helperText="The screen resolution of the device, e.g., 1920x1080"
111+
/>
112+
</Grid>
113+
<Grid item xs={12} sm={6}>
114+
<TextField
115+
fullWidth
116+
id="device-operating-system"
117+
label={Label.DeviceOperatingSystem}
118+
variant="outlined"
119+
size="small"
120+
value={device_operating_system || ""}
121+
onChange={e => setDeviceOperatingSystem(e.target.value)}
122+
helperText="The device's operating system, e.g., MacOS, Windows"
123+
/>
124+
</Grid>
125+
<Grid item xs={12} sm={6}>
126+
<TextField
127+
fullWidth
128+
id="device-operating-system-version"
129+
label={Label.DeviceOperatingSystemVersion}
130+
variant="outlined"
131+
size="small"
132+
value={device_operating_system_version || ""}
133+
onChange={e => setDeviceOperatingSystemVersion(e.target.value)}
134+
helperText="The version of the device's operating system, e.g., 13.5"
135+
/>
136+
</Grid>
137+
<Grid item xs={12} sm={6}>
138+
<TextField
139+
fullWidth
140+
id="device-model"
141+
label={Label.DeviceModel}
142+
variant="outlined"
143+
size="small"
144+
value={device_model || ""}
145+
onChange={e => setDeviceModel(e.target.value)}
146+
helperText="The model of the device, e.g., Pixel 6"
147+
/>
148+
</Grid>
149+
<Grid item xs={12} sm={6}>
150+
<TextField
151+
fullWidth
152+
id="device-brand"
153+
label={Label.DeviceBrand}
154+
variant="outlined"
155+
size="small"
156+
value={device_brand || ""}
157+
onChange={e => setDeviceBrand(e.target.value)}
158+
helperText="The brand of the device, e.g., Google"
159+
/>
160+
</Grid>
161+
<Grid item xs={12} sm={6}>
162+
<TextField
163+
fullWidth
164+
id="device-browser"
165+
label={Label.DeviceBrowser}
166+
variant="outlined"
167+
size="small"
168+
value={device_browser || ""}
169+
onChange={e => setDeviceBrowser(e.target.value)}
170+
helperText="The brand or type of browser, e.g., Chrome"
171+
/>
172+
</Grid>
173+
<Grid item xs={12} sm={6}>
174+
<TextField
175+
fullWidth
176+
id="device-browser-version"
177+
label={Label.DeviceBrowserVersion}
178+
variant="outlined"
179+
size="small"
180+
value={device_browser_version || ""}
181+
onChange={e => setDeviceBrowserVersion(e.target.value)}
182+
helperText="The browser version, e.g., 136.0.7103.60"
183+
/>
184+
</Grid>
185+
</Grid>
186+
{!useFirebase && (
187+
<>
188+
<Typography variant="h6">User Agent</Typography>
189+
<Typography>
190+
Provide a custom user agent string. Google Analytics will use this to derive information
191+
about the user's device, operating system, and browser. This field is ignored if device
192+
attributes are provided.
193+
</Typography>
194+
<Grid container spacing={1}>
195+
<Grid item xs={12} sm={6}>
196+
<TextField
197+
fullWidth
198+
id="user-agent"
199+
label={Label.UserAgent}
200+
variant="outlined"
201+
size="small"
202+
value={user_agent || ""}
203+
onChange={e => setUserAgent(e.target.value)}
204+
helperText="The user agent string identifying the client"
205+
/>
206+
</Grid>
207+
</Grid>
208+
</>
209+
)}
210+
</Root>
211+
)
212+
}
213+
214+
export default DeviceInformation

src/components/ga4/EventBuilder/ValidateEvent/index.spec.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,19 @@ const renderComponent = (props: Partial<ValidateEventProps> = {}) => {
6060
country_id: "US",
6161
subcontinent_id: "021",
6262
continent_id: "019"
63-
}
63+
},
64+
user_agent: "",
65+
device: {
66+
category: "mobile",
67+
language: "en",
68+
screen_resolution: "1280x2856",
69+
operating_system: "Android",
70+
operating_system_version: "14",
71+
model: "Pixel 9 Pro",
72+
brand: "Google",
73+
browser: "Chrome",
74+
browser_version: "136.0.7103.60"
75+
}
6476
}
6577

6678
return render(

src/components/ga4/EventBuilder/ValidateEvent/schemas/baseContent.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { userPropertiesSchema } from './userProperties'
44
import { eventsSchema } from './events'
55
import { userLocationSchema } from "./userLocation"
6+
import { deviceSchema } from "./deviceSchema"
67

78
export const baseContentSchema = {
89
type: "object",
@@ -31,5 +32,9 @@ export const baseContentSchema = {
3132
ip_override: {
3233
type: "string",
3334
},
35+
device: deviceSchema,
36+
user_agent: {
37+
type: "string",
38+
}
3439
},
3540
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export const deviceSchema = {
2+
type: "object",
3+
additionalProperties: false,
4+
properties: {
5+
category: {
6+
type: "string",
7+
},
8+
language: {
9+
type: "string",
10+
},
11+
screen_resolution: {
12+
type: "string",
13+
},
14+
operating_system: {
15+
type: "string",
16+
},
17+
operating_system_version: {
18+
type: "string",
19+
},
20+
model: {
21+
type: "string",
22+
},
23+
brand: {
24+
type: "string",
25+
},
26+
browser: {
27+
type: "string",
28+
},
29+
browser_version: {
30+
type: "string",
31+
},
32+
},
33+
};

src/components/ga4/EventBuilder/ValidateEvent/usePayload.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ const usePayload = (): {} => {
7474
payloadObj,
7575
ip_override,
7676
user_location,
77+
device,
78+
user_agent
7779
} = useContext(EventCtx)!
7880

7981
const eventName = useMemo(() => {
@@ -116,13 +118,24 @@ const usePayload = (): {} => {
116118
return cleaned_location
117119
}, [user_location])
118120

121+
const device_info = useMemo(() => {
122+
if (device === undefined) {
123+
return undefined
124+
}
125+
const cleaned_device = removeUndefined(device)
126+
if (Object.keys(cleaned_device).length === 0) {
127+
return undefined
128+
}
129+
return cleaned_device
130+
}, [device])
131+
119132
let payload = useMemo(() => {
120133
return {
121134
...removeUndefined(clientIds),
122135
...removeUndefined({ timestamp_micros }),
123136
...removeUndefined({ non_personalized_ads }),
124137
...removeUndefined(removeEmptyObject({ user_properties })),
125-
...removeUndefined({ ip_override, user_location: user_location_info }),
138+
...removeUndefined({ ip_override, user_location: user_location_info, device: device_info, user_agent }),
126139
events: [
127140
{ name: eventName, ...(parameters.length > 0 ? { params } : {}) },
128141
],
@@ -137,6 +150,8 @@ const usePayload = (): {} => {
137150
user_properties,
138151
ip_override,
139152
user_location_info,
153+
device_info,
154+
user_agent
140155
])
141156

142157
if (useTextBox) {

0 commit comments

Comments
 (0)