Skip to content

Commit ba03bc3

Browse files
committed
add support for geo fields
1 parent a79e34a commit ba03bc3

11 files changed

Lines changed: 664 additions & 368 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright 2020 Google Inc. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import React from "react"
16+
import Chip from "@mui/material/Chip";
17+
import Divider from '@mui/material/Divider';
18+
import { styled } from "@mui/material/styles"
19+
import Typography from "@mui/material/Typography"
20+
import TextField from "@mui/material/TextField"
21+
import Grid from "@mui/material/Grid"
22+
23+
import LinkedTextField from "@/components/LinkedTextField"
24+
import ExternalLink from "@/components/ExternalLink"
25+
import { Label } from "./types"
26+
27+
const Root = styled("div")(({ theme }) => ({
28+
marginTop: theme.spacing(3),
29+
}))
30+
31+
32+
interface GeographicInformationProps {
33+
user_location_city: string | undefined
34+
setUserLocationCity: (value: string) => void
35+
user_location_region_id: string | undefined
36+
setUserLocationRegionId: (value: string) => void
37+
user_location_country_id: string | undefined
38+
setUserLocationCountryId: (value: string) => void
39+
user_location_subcontinent_id: string | undefined
40+
setUserLocationSubcontinentId: (value: string) => void
41+
user_location_continent_id: string | undefined
42+
setUserLocationContinentId: (value: string) => void
43+
ip_override: string | undefined
44+
setIpOverride: (value: string) => void
45+
}
46+
47+
const GeographicInformation: React.FC<GeographicInformationProps> = ({
48+
user_location_city,
49+
setUserLocationCity,
50+
user_location_region_id,
51+
setUserLocationRegionId,
52+
user_location_country_id,
53+
setUserLocationCountryId,
54+
user_location_subcontinent_id,
55+
setUserLocationSubcontinentId,
56+
user_location_continent_id,
57+
setUserLocationContinentId,
58+
ip_override,
59+
setIpOverride,
60+
}) => {
61+
return (
62+
<Root>
63+
{/* <Typography variant="h4">Geographic Information</Typography> */}
64+
<Divider><Chip label="GEOGRAPHIC INFORMATION" size="small" /></Divider>
65+
<Typography variant="h6">User Location</Typography>
66+
<Typography>
67+
See the{" "}
68+
<ExternalLink href="https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference#user_location">
69+
documentation
70+
</ExternalLink>{" "}
71+
for more information about user location attributes.
72+
</Typography>
73+
<Grid container spacing={1}>
74+
<Grid item xs={12} sm={6}>
75+
<TextField
76+
fullWidth
77+
label={Label.City}
78+
id={Label.City}
79+
variant="outlined"
80+
size="small"
81+
value={user_location_city || ""}
82+
onChange={e => setUserLocationCity(e.target.value)}
83+
helperText="The city of the user. E.g. 'Mountain View'."
84+
/>
85+
</Grid>
86+
<Grid item xs={12} sm={6}>
87+
<TextField
88+
fullWidth
89+
label={Label.RegionId}
90+
id={Label.RegionId}
91+
variant="outlined"
92+
size="small"
93+
value={user_location_region_id || ""}
94+
onChange={e => setUserLocationRegionId(e.target.value)}
95+
helperText="The region of the user. E.g. 'US-CA'."
96+
/>
97+
</Grid>
98+
<Grid item xs={12} sm={6}>
99+
<TextField
100+
fullWidth
101+
label={Label.CountryId}
102+
id={Label.CountryId}
103+
variant="outlined"
104+
size="small"
105+
value={user_location_country_id || ""}
106+
onChange={e => setUserLocationCountryId(e.target.value)}
107+
helperText="The country of the user. E.g. 'US'."
108+
/>
109+
</Grid>
110+
<Grid item xs={12} sm={6}>
111+
<TextField
112+
fullWidth
113+
label={Label.SubcontinentId}
114+
id={Label.SubcontinentId}
115+
variant="outlined"
116+
size="small"
117+
value={user_location_subcontinent_id || ""}
118+
onChange={e => setUserLocationSubcontinentId(e.target.value)}
119+
helperText="The subcontinent of the user. E.g. '021'."
120+
/>
121+
</Grid>
122+
<Grid item xs={12} sm={6}>
123+
<TextField
124+
fullWidth
125+
label={Label.ContinentId}
126+
id={Label.ContinentId}
127+
variant="outlined"
128+
size="small"
129+
value={user_location_continent_id || ""}
130+
onChange={e => setUserLocationContinentId(e.target.value)}
131+
helperText="The continent of the user. E.g. '019'."
132+
/>
133+
</Grid>
134+
</Grid>
135+
<Typography variant="h6">IP Override</Typography>
136+
<Typography>
137+
Specify an IP address as an alternative to sending user location. This
138+
IP address will be used to derive the user's geographic information. If
139+
both an IP override and user location are provided, user location will
140+
be used.
141+
</Typography>
142+
<LinkedTextField
143+
label={Label.IpOverride}
144+
id={Label.IpOverride}
145+
linkTitle="See ip_override on devsite."
146+
href="https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference#ip_override"
147+
value={ip_override || ""}
148+
onChange={setIpOverride}
149+
helperText="The IP address of the user."
150+
/>
151+
</Root>
152+
)
153+
}
154+
155+
export default GeographicInformation

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import PrettyJson from "@/components/PrettyJson"
2727
import usePayload from "./usePayload"
2828
import { ValidationMessage } from "../types"
2929
import Spinner from "@/components/Spinner"
30-
import { EventCtx, Label } from ".."
30+
import { EventCtx } from ".."
31+
import { Label } from "../types"
3132
import { Card } from "@mui/material"
3233
import { green, red } from "@mui/material/colors"
3334

src/components/ga4/EventBuilder/ValidateEvent/schemas/userLocation.spec.ts

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,55 +23,47 @@ describe("userLocationSchema", () => {
2323
}
2424
)
2525

26-
test.each([["USA-CA"], ["US-CALI"], ["us-ca"], ["US_CA"]])(
27-
"is invalid with an invalid region_id: %s",
28-
region_id => {
29-
const validator = new Validator(userLocationSchema)
30-
expect(validator.isValid({ region_id })).toEqual(false)
31-
}
32-
)
26+
test("is invalid with an invalid region_id", () => {
27+
const invalidInput = { region_id: 123 }
28+
const validator = new Validator(userLocationSchema)
29+
expect(validator.isValid(invalidInput)).toEqual(false)
30+
})
3331

3432
test("is valid with a valid country_id", () => {
3533
const validInput = { country_id: "US" }
3634
const validator = new Validator(userLocationSchema)
3735
expect(validator.isValid(validInput)).toEqual(true)
3836
})
3937

40-
test.each([["USA"], ["U"], ["us"]])(
41-
"is invalid with an invalid country_id: %s",
42-
country_id => {
43-
const validator = new Validator(userLocationSchema)
44-
expect(validator.isValid({ country_id })).toEqual(false)
45-
}
46-
)
38+
test("is invalid with an invalid country_id", () => {
39+
const invalidInput = { country_id: 1 }
40+
const validator = new Validator(userLocationSchema)
41+
expect(validator.isValid(invalidInput)).toEqual(false)
42+
})
4743

4844
test("is valid with a valid subcontinent_id", () => {
4945
const validInput = { subcontinent_id: "021" }
5046
const validator = new Validator(userLocationSchema)
5147
expect(validator.isValid(validInput)).toEqual(true)
5248
})
5349

54-
test.each([["21"], ["0211"], ["abc"]])(
55-
"is invalid with an invalid subcontinent_id: %s",
56-
subcontinent_id => {
57-
const validator = new Validator(userLocationSchema)
58-
expect(validator.isValid({ subcontinent_id })).toEqual(false)
59-
}
60-
)
50+
test("is invalid with an invalid subcontinent_id", () => {
51+
const validInput = { subcontinent_id: 541 }
52+
const validator = new Validator(userLocationSchema)
53+
expect(validator.isValid(validInput)).toEqual(false)
54+
})
6155

6256
test("is valid with a valid continent_id", () => {
6357
const validInput = { continent_id: "019" }
6458
const validator = new Validator(userLocationSchema)
6559
expect(validator.isValid(validInput)).toEqual(true)
6660
})
6761

68-
test.each([["19"], ["0199"], ["abc"]])(
69-
"is invalid with an invalid continent_id: %s",
70-
continent_id => {
71-
const validator = new Validator(userLocationSchema)
72-
expect(validator.isValid({ continent_id })).toEqual(false)
73-
}
74-
)
62+
test("is invalid with an invalid continent_id", () => {
63+
const validInput = { continent_id: 540 }
64+
const validator = new Validator(userLocationSchema)
65+
expect(validator.isValid(validInput)).toEqual(false)
66+
})
7567

7668
test("is invalid with additional properties", () => {
7769
const invalidInput = {

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,15 @@ export const userLocationSchema = {
99
},
1010
region_id: {
1111
type: "string",
12-
// ISO 3166-2
13-
pattern: "^[A-Z]{2}-[A-Z0-9]{1,3}$",
1412
},
1513
country_id: {
1614
type: "string",
17-
// ISO 3166-1 alpha-2
18-
pattern: "^[A-Z]{2}$",
1915
},
2016
subcontinent_id: {
2117
type: "string",
22-
// UN M49
23-
pattern: "^[0-9]{3}$",
2418
},
2519
continent_id: {
2620
type: "string",
27-
// UN M49
28-
pattern: "^[0-9]{3}$",
2921
},
3022
},
3123
}

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ const usePayload = (): {} => {
7171
clientIds,
7272
type,
7373
useTextBox,
74-
payloadObj
74+
payloadObj,
75+
ip_override,
76+
user_location,
7577
} = useContext(EventCtx)!
7678

7779
const eventName = useMemo(() => {
@@ -93,22 +95,34 @@ const usePayload = (): {} => {
9395
[items]
9496
)
9597

96-
const params = useMemo(() => parameters.reduce(objectify, itemsParameter), [
97-
parameters,
98-
itemsParameter,
99-
])
98+
const params = useMemo(
99+
() => parameters.reduce(objectify, itemsParameter),
100+
[parameters, itemsParameter]
101+
)
100102

101103
const user_properties = useMemo(
102104
() => userProperties.reduce(objectifyUserProperties, {}),
103105
[userProperties]
104106
)
105107

108+
const final_user_location = useMemo(() => {
109+
if (user_location === undefined) {
110+
return undefined
111+
}
112+
const cleaned_location = removeUndefined(user_location)
113+
if (Object.keys(cleaned_location).length === 0) {
114+
return undefined
115+
}
116+
return cleaned_location
117+
}, [user_location])
118+
106119
let payload = useMemo(() => {
107120
return {
108121
...removeUndefined(clientIds),
109122
...removeUndefined({ timestamp_micros }),
110123
...removeUndefined({ non_personalized_ads }),
111124
...removeUndefined(removeEmptyObject({ user_properties })),
125+
...removeUndefined({ ip_override, user_location: final_user_location }),
112126
events: [
113127
{ name: eventName, ...(parameters.length > 0 ? { params } : {}) },
114128
],
@@ -121,6 +135,8 @@ const usePayload = (): {} => {
121135
params,
122136
timestamp_micros,
123137
user_properties,
138+
ip_override,
139+
final_user_location,
124140
])
125141

126142
if (useTextBox) {

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const useSharableLink = () => {
1717
parameters,
1818
eventName,
1919
type,
20+
ip_override,
21+
user_location,
2022
} = useContext(EventCtx)!
2123

2224
return useMemo(() => {
@@ -57,6 +59,19 @@ const useSharableLink = () => {
5759

5860
addIfTruthy(UrlParam.TimestampMicros, timestamp_micros)
5961

62+
addIfTruthy(UrlParam.IpOverride, ip_override)
63+
64+
if (user_location) {
65+
addIfTruthy(UrlParam.UserLocationCity, user_location.city)
66+
addIfTruthy(UrlParam.UserLocationRegionId, user_location.region_id)
67+
addIfTruthy(UrlParam.UserLocationCountryId, user_location.country_id)
68+
addIfTruthy(
69+
UrlParam.UserLocationSubcontinentId,
70+
user_location.subcontinent_id
71+
)
72+
addIfTruthy(UrlParam.UserLocationContinentId, user_location.continent_id)
73+
}
74+
6075
if (userProperties) {
6176
params.append(UrlParam.UserProperties, encodeObject(userProperties))
6277
}
@@ -84,6 +99,8 @@ const useSharableLink = () => {
8499
api_secret,
85100
timestamp_micros,
86101
non_personalized_ads,
102+
ip_override,
103+
user_location,
87104
])
88105
}
89106

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import * as renderer from "@testing-library/react"
1818
import "@testing-library/jest-dom"
1919

2020
import { withProviders } from "@/test-utils"
21-
import Sut, { Label } from "./index"
21+
import Sut from "./index"
22+
import { Label } from "./types"
2223
import userEvent from "@testing-library/user-event"
2324
import { within } from "@testing-library/react"
2425

0 commit comments

Comments
 (0)