Skip to content

Commit c8da06f

Browse files
authored
Add TextBox to Event Builder (#1657)
* Add payload textbox to Event Builder * Create payload textbox, begin payload formatting logic * Added format Payload button; able to format payload * Add format Payload ability before validate event * Payload is now being validated, bug still bugs * Able to validate payload from textbox on first entry * Add payload formatting errors * Fix formatting issues * Remove console logs * Cleanup * Fix typescript errors * Improve design, fix linter errors * Only validate payload for textbox entries * Validate api_secret exists * Fix linter errors * Update naming * Change payload URL * Update styling
1 parent 77e25a9 commit c8da06f

13 files changed

Lines changed: 746 additions & 337 deletions

File tree

src/components/Buttons.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,13 @@ export const PlainButton: React.FC<Props> = ({ ...props }) => {
6565
}
6666

6767
export const TooltipIconButton: React.FC<{
68-
tooltip: string
68+
tooltip: any
6969
size?: "small" | "medium"
7070
className?: string
7171
disabled?: boolean
72+
placement?: "bottom" | "left" | "right" | "top" | "bottom-end" | "bottom-start" | "left-end" | "left-start" | "right-end" | "right-start" | "top-end" | "top-start" | undefined
7273
onClick?: () => void
73-
}> = ({ tooltip, children, onClick, className, disabled, size = "small" }) => {
74+
}> = ({ tooltip, children, onClick, className, disabled, size = "small", placement='bottom'}) => {
7475
if (disabled) {
7576
return (
7677
<IconButton
@@ -84,7 +85,7 @@ export const TooltipIconButton: React.FC<{
8485
)
8586
}
8687
return (
87-
<Tooltip title={tooltip}>
88+
<Tooltip title={tooltip} placement={placement} leaveDelay={2000}>
8889
<IconButton onClick={onClick} size={size} className={className}>
8990
{children}
9091
</IconButton>

src/components/CampaignURLBuilder/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { GAVersion } from "@/constants"
2222
import TabPanel from "@/components/TabPanel"
2323
import WebURLBuilder from "./Web"
2424
import PlayURLBuilder from "./Play"
25-
import IOSURLBuilder from "./IOS"
2625

2726
export enum URLBuilderType {
2827
Web = "web",

src/components/TextBox.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from "react"
2+
import { TextField } from "@material-ui/core"
3+
import ExternalLink from "./ExternalLink"
4+
5+
export interface TextBoxProps {
6+
href: string
7+
linkTitle: string
8+
value: string | undefined | object
9+
label: string
10+
onChange: (e: string) => void
11+
helperText?: string | JSX.Element
12+
extraAction?: JSX.Element
13+
required?: true
14+
disabled?: boolean
15+
id?: string
16+
}
17+
18+
const TextBox: React.FC<TextBoxProps> = ({
19+
href,
20+
linkTitle,
21+
label,
22+
value,
23+
onChange,
24+
required,
25+
helperText,
26+
disabled,
27+
extraAction,
28+
id,
29+
}) => {
30+
return (
31+
<TextField
32+
InputProps={{
33+
endAdornment: (
34+
<span
35+
style={{
36+
display: "inline-flex",
37+
}}
38+
>
39+
{extraAction}
40+
<ExternalLink href={href} title={linkTitle} hover />
41+
</span>
42+
),
43+
}}
44+
id={id}
45+
size="medium"
46+
variant="outlined"
47+
fullWidth
48+
label={label}
49+
value={value === undefined ? "" : value}
50+
onChange={e => onChange(e.target.value)}
51+
required={required}
52+
helperText={helperText}
53+
disabled={disabled}
54+
multiline={true}
55+
maxRows={15}
56+
minRows={15}
57+
/>
58+
)
59+
}
60+
61+
export default TextBox

src/components/ga4/EventBuilder/ValidateEvent/handlers/formatCheckLib.spec.ts

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ describe("formatCheckLib", () => {
66
test("does not return an error when app_instance_id is 32 alpha-numeric chars", () => {
77
const payload = {app_instance_id: "12345678901234567890123456789012"}
88
const firebaseAppId = '1:1233455666:android:abcdefgh'
9+
const api_secret = '123'
910

10-
let errors = formatCheckLib(payload, firebaseAppId)
11+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
1112

1213
expect(errors).toEqual([])
1314
})
@@ -16,8 +17,9 @@ describe("formatCheckLib", () => {
1617
const appInstanceId = "123456789012345678901234567890123"
1718
const payload = {app_instance_id: appInstanceId}
1819
const firebaseAppId = '1:1233455666:android:abcdefgh'
20+
const api_secret = '123'
1921

20-
let errors = formatCheckLib(payload, firebaseAppId)
22+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
2123

2224
expect(errors[0].description).toEqual(
2325
`Measurement app_instance_id is expected to be a 32 digit hexadecimal number but was [${ appInstanceId.length }] digits.`
@@ -28,8 +30,9 @@ describe("formatCheckLib", () => {
2830
const appInstanceId = "1234567890123456789012345678901g"
2931
const payload = {app_instance_id: appInstanceId}
3032
const firebaseAppId = '1:1233455666:android:abcdefgh'
33+
const api_secret = '123'
3134

32-
let errors = formatCheckLib(payload, firebaseAppId)
35+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
3336

3437
expect(errors[0].description).toEqual(
3538
`Measurement app_instance_id contains non hexadecimal character [g].`,
@@ -41,17 +44,19 @@ describe("formatCheckLib", () => {
4144
test("does not return an error for a valid event name", () => {
4245
const payload = {events: [{name: 'add_payment_info'}]}
4346
const firebaseAppId = '1:1233455666:android:abcdefgh'
47+
const api_secret = '123'
4448

45-
let errors = formatCheckLib(payload, firebaseAppId)
49+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
4650

4751
expect(errors).toEqual([])
4852
})
4953

5054
test("returns an error when event's name is a reserved name", () => {
5155
const payload = {events: [{name: 'ad_click'}]}
5256
const firebaseAppId = '1:1233455666:android:abcdefgh'
57+
const api_secret = '123'
5358

54-
let errors = formatCheckLib(payload, firebaseAppId)
59+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
5560

5661
expect(errors[0].description).toEqual(
5762
"ad_click is a reserved event name"
@@ -63,17 +68,19 @@ describe("formatCheckLib", () => {
6368
test("does not return an error for a valid user property name", () => {
6469
const payload = {user_properties: {'test': 'test'}}
6570
const firebaseAppId = '1:1233455666:android:abcdefgh'
71+
const api_secret = '123'
6672

67-
let errors = formatCheckLib(payload, firebaseAppId)
73+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
6874

6975
expect(errors).toEqual([])
7076
})
7177

7278
test("returns an error when event's name is a reserved name", () => {
7379
const payload = {user_properties: {'first_open_time': 'test'}}
7480
const firebaseAppId = '1:1233455666:android:abcdefgh'
81+
const api_secret = '123'
7582

76-
let errors = formatCheckLib(payload, firebaseAppId)
83+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
7784

7885
expect(errors[0].description).toEqual(
7986
"user_property: 'first_open_time' is a reserved user property name"
@@ -85,17 +92,19 @@ describe("formatCheckLib", () => {
8592
test("does not return an error for a valid currency type", () => {
8693
const payload = {events: [{params: {currency: 'USD'}}]}
8794
const firebaseAppId = '1:1233455666:android:abcdefgh'
95+
const api_secret = '123'
8896

89-
let errors = formatCheckLib(payload, firebaseAppId)
97+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
9098

9199
expect(errors).toEqual([])
92100
})
93101

94102
test("returns an error for an invalid currency type", () => {
95103
const payload = {events: [{params: {currency: 'USDD'}}]}
96104
const firebaseAppId = '1:1233455666:android:abcdefgh'
105+
const api_secret = '123'
97106

98-
let errors = formatCheckLib(payload, firebaseAppId)
107+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
99108

100109
expect(errors[0].description).toEqual(
101110
"currency: USDD must be a valid uppercase 3-letter ISO 4217 format"
@@ -107,17 +116,19 @@ describe("formatCheckLib", () => {
107116
test("does not return an error if items array is valid", () => {
108117
const payload = {events: [{params: {items: [{'item_id': 1234}]}}]}
109118
const firebaseAppId = '1:1233455666:android:abcdefgh'
119+
const api_secret = '123'
110120

111-
let errors = formatCheckLib(payload, firebaseAppId)
121+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
112122

113123
expect(errors).toEqual([])
114124
})
115125

116126
test("returns an error when items does not have either item_id or item_name", () => {
117127
const payload = {events: [{params: {items: [{'item_namee': 'test'}]}}]}
118128
const firebaseAppId = '1:1233455666:android:abcdefgh'
129+
const api_secret = '123'
119130

120-
let errors = formatCheckLib(payload, firebaseAppId)
131+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
121132

122133
expect(errors[0].description).toEqual(
123134
"'items' object must contain one of the following keys: 'item_id' or 'item_name'"
@@ -127,8 +138,9 @@ describe("formatCheckLib", () => {
127138
test("validates empty items array when event requires items", () => {
128139
const payload = {events: [{params: {name: 'purchase', items: []}}]}
129140
const firebaseAppId = '1:1233455666:android:abcdefgh'
141+
const api_secret = '123'
130142

131-
let errors = formatCheckLib(payload, firebaseAppId)
143+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
132144

133145
expect(errors[0].description).toEqual(
134146
"'items' should not be empty; One of 'item_id' or 'item_name' is a required key"
@@ -138,17 +150,19 @@ describe("formatCheckLib", () => {
138150
test("does not validate empty items array when event doesn't require items", () => {
139151
const payload = {events: [{params: {name: 'random', items: []}}]}
140152
const firebaseAppId = '1:1233455666:android:abcdefgh'
153+
const api_secret = '123'
141154

142-
let errors = formatCheckLib(payload, firebaseAppId)
155+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
143156

144157
expect(errors).toEqual([])
145158
})
146159

147160
test("does not validate empty items array when event does not have a name", () => {
148161
const payload = {events: [{params: {items: []}}]}
149162
const firebaseAppId = '1:1233455666:android:abcdefgh'
163+
const api_secret = '123'
150164

151-
let errors = formatCheckLib(payload, firebaseAppId)
165+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
152166

153167
expect(errors).toEqual([])
154168
})
@@ -169,8 +183,9 @@ describe("formatCheckLib", () => {
169183
]
170184
}
171185
const firebaseAppId = '1:1233455666:android:abcdefgh'
186+
const api_secret = '123'
172187

173-
let errors = formatCheckLib(payload, firebaseAppId)
188+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
174189

175190
expect(errors[0].description).toEqual(
176191
"'items' object must contain one of the following keys: 'item_id' or 'item_name'"
@@ -182,21 +197,47 @@ describe("formatCheckLib", () => {
182197
test("does not return an error if firebase_app_id is valid", () => {
183198
const payload = {}
184199
const firebaseAppId = '1:1233455666:android:abcdefgh'
200+
const api_secret = '123'
185201

186-
let errors = formatCheckLib(payload, firebaseAppId)
202+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
187203

188204
expect(errors).toEqual([])
189205
})
190206

191207
test("returns an error when firebase_app_id is invalid", () => {
192208
const payload = {}
193209
const firebaseAppId = '1233455666:android:abcdefgh'
210+
const api_secret = '123'
194211

195-
let errors = formatCheckLib(payload, firebaseAppId)
212+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
196213

197214
expect(errors[0].description).toEqual(
198215
`${firebaseAppId} does not follow firebase_app_id pattern of X:XX:XX:XX at path`
199216
)
200217
})
201218
})
219+
220+
describe("validates api_secret", () => {
221+
test("does not return an error api_secret is not null", () => {
222+
const payload = {}
223+
const firebaseAppId = '1:1233455666:android:abcdefgh'
224+
const api_secret = '123'
225+
226+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
227+
228+
expect(errors).toEqual([])
229+
})
230+
231+
test("returns an error when api_secret is null", () => {
232+
const payload = {}
233+
const firebaseAppId = '1:1233455666:android:abcdefgh'
234+
const api_secret = ''
235+
236+
let errors = formatCheckLib(payload, firebaseAppId, api_secret)
237+
238+
expect(errors[0].description).toEqual(
239+
"Unable to find non-empty parameter [api_secret] value in request."
240+
)
241+
})
242+
})
202243
})

src/components/ga4/EventBuilder/ValidateEvent/handlers/formatCheckLib.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ const RESERVED_USER_PROPERTY_NAMES = [
1717
]
1818

1919
// formatCheckLib provides additional validations for payload not included in
20-
// the schema validations. All checks are consistent with Firebase documentation
21-
export const formatCheckLib = (payload, firebaseAppId) => {
20+
// the schema validations. All checks are consistent with Firebase documentation.
21+
export const formatCheckLib = (payload, firebaseAppId, api_secret) => {
2222
let errors: ValidationMessage[] = []
2323

2424
const appInstanceIdErrors = isValidAppInstanceId(payload)
@@ -28,6 +28,7 @@ export const formatCheckLib = (payload, firebaseAppId) => {
2828
const emptyItemsErrors = isItemsEmpty(payload)
2929
const itemsRequiredKeyErrors = itemsHaveRequiredKey(payload)
3030
const firebaseAppIdErrors = isfirebaseAppIdValid(firebaseAppId)
31+
const apiSecretErrors = isApiSecretNotNull(api_secret)
3132
const sizeErrors = isTooBig(payload)
3233

3334
return [
@@ -39,6 +40,7 @@ export const formatCheckLib = (payload, firebaseAppId) => {
3940
...emptyItemsErrors,
4041
...itemsRequiredKeyErrors,
4142
...firebaseAppIdErrors,
43+
...apiSecretErrors,
4244
...sizeErrors,
4345
]
4446
}
@@ -183,7 +185,7 @@ const requiredKeysEmpty = (itemsObj) => {
183185
const isfirebaseAppIdValid = (firebaseAppId) => {
184186
let errors: ValidationMessage[] = []
185187

186-
if (!firebaseAppId.match(/[0-9]:[0-9]+:[a-zA-Z]+:[a-zA-Z0-9]+$/)) {
188+
if (firebaseAppId && !firebaseAppId.match(/[0-9]:[0-9]+:[a-zA-Z]+:[a-zA-Z0-9]+$/)) {
187189
errors.push({
188190
description: `${firebaseAppId} does not follow firebase_app_id pattern of X:XX:XX:XX at path`,
189191
validationCode: "value_invalid",
@@ -194,6 +196,20 @@ const isfirebaseAppIdValid = (firebaseAppId) => {
194196
return errors
195197
}
196198

199+
const isApiSecretNotNull = (api_secret) => {
200+
let errors: ValidationMessage[] = []
201+
202+
if (!api_secret) {
203+
errors.push({
204+
description: "Unable to find non-empty parameter [api_secret] value in request.",
205+
validationCode: "VALUE_REQUIRED",
206+
fieldPath: "api_secret"
207+
})
208+
}
209+
210+
return errors
211+
}
212+
197213
const isTooBig = (payload) => {
198214
let errors: ValidationMessage[] = []
199215

0 commit comments

Comments
 (0)