diff --git a/src/components/ga4/EventBuilder/DeviceInformation.tsx b/src/components/ga4/EventBuilder/DeviceInformation.tsx new file mode 100644 index 00000000..85f77c78 --- /dev/null +++ b/src/components/ga4/EventBuilder/DeviceInformation.tsx @@ -0,0 +1,214 @@ +import React, { useContext } from "react" +import Chip from "@mui/material/Chip" +import Divider from "@mui/material/Divider" +import { styled } from "@mui/material/styles" +import Typography from "@mui/material/Typography" +import TextField from "@mui/material/TextField" +import Grid from "@mui/material/Grid" + +import ExternalLink from "@/components/ExternalLink" +import { Label } from "./types" +import { UseFirebaseCtx } from "." + +const Root = styled("div")(({ theme }) => ({ + marginTop: theme.spacing(1), +})) + +type DeviceInformationProps = { + device_category: string | undefined + setDeviceCategory: (value: string) => void + device_language: string | undefined + setDeviceLanguage: (value: string) => void + device_screen_resolution: string | undefined + setDeviceScreenResolution: (value: string) => void + device_operating_system: string | undefined + setDeviceOperatingSystem: (value: string) => void + device_operating_system_version: string | undefined + setDeviceOperatingSystemVersion: (value: string) => void + device_model: string | undefined + setDeviceModel: (value: string) => void + device_brand: string | undefined + setDeviceBrand: (value: string) => void + device_browser: string | undefined + setDeviceBrowser: (value: string) => void + device_browser_version: string | undefined + setDeviceBrowserVersion: (value: string) => void + user_agent: string | undefined + setUserAgent: (value: string) => void +} + +const DeviceInformation: React.FC = ({ + device_category, + setDeviceCategory, + device_language, + setDeviceLanguage, + device_screen_resolution, + setDeviceScreenResolution, + device_operating_system, + setDeviceOperatingSystem, + device_operating_system_version, + setDeviceOperatingSystemVersion, + device_model, + setDeviceModel, + device_brand, + setDeviceBrand, + device_browser, + setDeviceBrowser, + device_browser_version, + setDeviceBrowserVersion, + user_agent, + setUserAgent, +}) => { + const useFirebase = useContext(UseFirebaseCtx) + const docHref = + "https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference#device" + return ( + + + + + Device Attributes + + See the{" "} + documentation for more + information about device attributes. + + + + setDeviceCategory(e.target.value)} + helperText="The category of the device, e.g., mobile, desktop" + /> + + + setDeviceLanguage(e.target.value)} + helperText="The language of the device in ISO 639-1 format, e.g., en" + /> + + + setDeviceScreenResolution(e.target.value)} + helperText="The screen resolution of the device, e.g., 1920x1080" + /> + + + setDeviceOperatingSystem(e.target.value)} + helperText="The device's operating system, e.g., MacOS, Windows" + /> + + + setDeviceOperatingSystemVersion(e.target.value)} + helperText="The version of the device's operating system, e.g., 13.5" + /> + + + setDeviceModel(e.target.value)} + helperText="The model of the device, e.g., Pixel 6" + /> + + + setDeviceBrand(e.target.value)} + helperText="The brand of the device, e.g., Google" + /> + + + setDeviceBrowser(e.target.value)} + helperText="The brand or type of browser, e.g., Chrome" + /> + + + setDeviceBrowserVersion(e.target.value)} + helperText="The browser version, e.g., 136.0.7103.60" + /> + + + {!useFirebase && ( + <> + User Agent + + Provide a custom user agent string. Google Analytics will use this to derive information + about the user's device, operating system, and browser. This field is ignored if device + attributes are provided. + + + + setUserAgent(e.target.value)} + helperText="The user agent string identifying the client" + /> + + + + )} + + ) +} + +export default DeviceInformation \ No newline at end of file diff --git a/src/components/ga4/EventBuilder/ValidateEvent/index.spec.tsx b/src/components/ga4/EventBuilder/ValidateEvent/index.spec.tsx index 7df63e0b..1b0e3207 100644 --- a/src/components/ga4/EventBuilder/ValidateEvent/index.spec.tsx +++ b/src/components/ga4/EventBuilder/ValidateEvent/index.spec.tsx @@ -60,7 +60,19 @@ const renderComponent = (props: Partial = {}) => { country_id: "US", subcontinent_id: "021", continent_id: "019" - } + }, + user_agent: "", + device: { + category: "mobile", + language: "en", + screen_resolution: "1280x2856", + operating_system: "Android", + operating_system_version: "14", + model: "Pixel 9 Pro", + brand: "Google", + browser: "Chrome", + browser_version: "136.0.7103.60" + } } return render( diff --git a/src/components/ga4/EventBuilder/ValidateEvent/schemas/baseContent.ts b/src/components/ga4/EventBuilder/ValidateEvent/schemas/baseContent.ts index 0245e26b..8ce78c0b 100644 --- a/src/components/ga4/EventBuilder/ValidateEvent/schemas/baseContent.ts +++ b/src/components/ga4/EventBuilder/ValidateEvent/schemas/baseContent.ts @@ -3,6 +3,7 @@ import { userPropertiesSchema } from './userProperties' import { eventsSchema } from './events' import { userLocationSchema } from "./userLocation" +import { deviceSchema } from "./deviceSchema" export const baseContentSchema = { type: "object", @@ -31,5 +32,9 @@ export const baseContentSchema = { ip_override: { type: "string", }, + device: deviceSchema, + user_agent: { + type: "string", + } }, } \ No newline at end of file diff --git a/src/components/ga4/EventBuilder/ValidateEvent/schemas/deviceSchema.ts b/src/components/ga4/EventBuilder/ValidateEvent/schemas/deviceSchema.ts new file mode 100644 index 00000000..4818bc09 --- /dev/null +++ b/src/components/ga4/EventBuilder/ValidateEvent/schemas/deviceSchema.ts @@ -0,0 +1,33 @@ +export const deviceSchema = { + type: "object", + additionalProperties: false, + properties: { + category: { + type: "string", + }, + language: { + type: "string", + }, + screen_resolution: { + type: "string", + }, + operating_system: { + type: "string", + }, + operating_system_version: { + type: "string", + }, + model: { + type: "string", + }, + brand: { + type: "string", + }, + browser: { + type: "string", + }, + browser_version: { + type: "string", + }, + }, +}; diff --git a/src/components/ga4/EventBuilder/ValidateEvent/usePayload.ts b/src/components/ga4/EventBuilder/ValidateEvent/usePayload.ts index 854cf155..236653f5 100644 --- a/src/components/ga4/EventBuilder/ValidateEvent/usePayload.ts +++ b/src/components/ga4/EventBuilder/ValidateEvent/usePayload.ts @@ -74,6 +74,8 @@ const usePayload = (): {} => { payloadObj, ip_override, user_location, + device, + user_agent } = useContext(EventCtx)! const eventName = useMemo(() => { @@ -116,13 +118,24 @@ const usePayload = (): {} => { return cleaned_location }, [user_location]) + const device_info = useMemo(() => { + if (device === undefined) { + return undefined + } + const cleaned_device = removeUndefined(device) + if (Object.keys(cleaned_device).length === 0) { + return undefined + } + return cleaned_device + }, [device]) + let payload = useMemo(() => { return { ...removeUndefined(clientIds), ...removeUndefined({ timestamp_micros }), ...removeUndefined({ non_personalized_ads }), ...removeUndefined(removeEmptyObject({ user_properties })), - ...removeUndefined({ ip_override, user_location: user_location_info }), + ...removeUndefined({ ip_override, user_location: user_location_info, device: device_info, user_agent }), events: [ { name: eventName, ...(parameters.length > 0 ? { params } : {}) }, ], @@ -137,6 +150,8 @@ const usePayload = (): {} => { user_properties, ip_override, user_location_info, + device_info, + user_agent ]) if (useTextBox) { diff --git a/src/components/ga4/EventBuilder/index.tsx b/src/components/ga4/EventBuilder/index.tsx index b4550205..530ac169 100644 --- a/src/components/ga4/EventBuilder/index.tsx +++ b/src/components/ga4/EventBuilder/index.tsx @@ -44,6 +44,7 @@ import ValidateEvent from "./ValidateEvent" import { PlainButton } from "@/components/Buttons" import { useEffect } from "react" import GeographicInformation from "./GeographicInformation"; +import DeviceInformation from "./DeviceInformation"; const PREFIX = 'EventBuilder'; @@ -124,6 +125,7 @@ export type EventPayload = { useTextBox: boolean payloadObj: any ip_override: string | undefined + user_agent: string | undefined user_location: { city: string | undefined region_id: string | undefined @@ -131,6 +133,17 @@ export type EventPayload = { subcontinent_id: string | undefined continent_id: string | undefined } + device: { + category: string | undefined + language: string | undefined + screen_resolution: string | undefined + operating_system: string | undefined + operating_system_version: string | undefined + model: string | undefined + brand: string | undefined + browser: string | undefined + browser_version: string | undefined + } } export const EventCtx = React.createContext< | EventPayload @@ -217,6 +230,17 @@ const EventBuilder: React.FC = () => { const [user_location_continent_id, setUserLocationContinentId] = React.useState("") const [ip_override, setIpOverride] = React.useState("") + const [device_category, setDeviceCategory] = React.useState("") + const [device_language, setDeviceLanguage] = React.useState("") + const [device_screen_resolution, setDeviceScreenResolution] = React.useState("") + const [device_operating_system, setDeviceOperatingSystem] = React.useState("") + const [device_operating_system_version, setDeviceOperatingSystemVersion] = React.useState("") + const [device_model, setDeviceModel] = React.useState("") + const [device_brand, setDeviceBrand] = React.useState("") + const [device_browser, setDeviceBrowser] = React.useState("") + const [device_browser_version, setDeviceBrowserVersion] = React.useState("") + const [user_agent, setUserAgent] = React.useState("") + const formatPayload = React.useCallback(() => { try { if (inputPayload) { @@ -226,6 +250,7 @@ const EventBuilder: React.FC = () => { } else { setPayloadErrors("Empty Payload") setPayloadObj({}) + setPayloadErrors("") } } catch (err: any) { setPayloadErrors(err.message) @@ -609,7 +634,7 @@ const EventBuilder: React.FC = () => { )} {showAdvanced && ( - <> + { ip_override={ip_override} setIpOverride={setIpOverride} /> - + + )} @@ -654,6 +705,7 @@ const EventBuilder: React.FC = () => { payloadObj, instanceId: useFirebase ? { firebase_app_id } : { measurement_id }, api_secret: api_secret!, + user_agent, ip_override, user_location: { city: user_location_city, @@ -662,6 +714,17 @@ const EventBuilder: React.FC = () => { subcontinent_id: user_location_subcontinent_id, continent_id: user_location_continent_id, }, + device: { + category: device_category, + language: device_language, + screen_resolution: device_screen_resolution, + operating_system: device_operating_system, + operating_system_version: device_operating_system_version, + model: device_model, + brand: device_brand, + browser: device_browser, + browser_version: device_browser_version, + }, }} >