Skip to content

Commit

Permalink
Add TextBox to Event Builder (#1657)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
marycodes2 authored Jun 29, 2023
1 parent 77e25a9 commit c8da06f
Show file tree
Hide file tree
Showing 13 changed files with 746 additions and 337 deletions.
7 changes: 4 additions & 3 deletions src/components/Buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ export const PlainButton: React.FC<Props> = ({ ...props }) => {
}

export const TooltipIconButton: React.FC<{
tooltip: string
tooltip: any
size?: "small" | "medium"
className?: string
disabled?: boolean
placement?: "bottom" | "left" | "right" | "top" | "bottom-end" | "bottom-start" | "left-end" | "left-start" | "right-end" | "right-start" | "top-end" | "top-start" | undefined
onClick?: () => void
}> = ({ tooltip, children, onClick, className, disabled, size = "small" }) => {
}> = ({ tooltip, children, onClick, className, disabled, size = "small", placement='bottom'}) => {
if (disabled) {
return (
<IconButton
Expand All @@ -84,7 +85,7 @@ export const TooltipIconButton: React.FC<{
)
}
return (
<Tooltip title={tooltip}>
<Tooltip title={tooltip} placement={placement} leaveDelay={2000}>
<IconButton onClick={onClick} size={size} className={className}>
{children}
</IconButton>
Expand Down
1 change: 0 additions & 1 deletion src/components/CampaignURLBuilder/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { GAVersion } from "@/constants"
import TabPanel from "@/components/TabPanel"
import WebURLBuilder from "./Web"
import PlayURLBuilder from "./Play"
import IOSURLBuilder from "./IOS"

export enum URLBuilderType {
Web = "web",
Expand Down
61 changes: 61 additions & 0 deletions src/components/TextBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from "react"
import { TextField } from "@material-ui/core"
import ExternalLink from "./ExternalLink"

export interface TextBoxProps {
href: string
linkTitle: string
value: string | undefined | object
label: string
onChange: (e: string) => void
helperText?: string | JSX.Element
extraAction?: JSX.Element
required?: true
disabled?: boolean
id?: string
}

const TextBox: React.FC<TextBoxProps> = ({
href,
linkTitle,
label,
value,
onChange,
required,
helperText,
disabled,
extraAction,
id,
}) => {
return (
<TextField
InputProps={{
endAdornment: (
<span
style={{
display: "inline-flex",
}}
>
{extraAction}
<ExternalLink href={href} title={linkTitle} hover />
</span>
),
}}
id={id}
size="medium"
variant="outlined"
fullWidth
label={label}
value={value === undefined ? "" : value}
onChange={e => onChange(e.target.value)}
required={required}
helperText={helperText}
disabled={disabled}
multiline={true}
maxRows={15}
minRows={15}
/>
)
}

export default TextBox
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ describe("formatCheckLib", () => {
test("does not return an error when app_instance_id is 32 alpha-numeric chars", () => {
const payload = {app_instance_id: "12345678901234567890123456789012"}
const firebaseAppId = '1:1233455666:android:abcdefgh'
const api_secret = '123'

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors).toEqual([])
})

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors).toEqual([])
})

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors).toEqual([])
})

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors).toEqual([])
})

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors).toEqual([])
})

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors).toEqual([])
})
Expand All @@ -169,8 +183,9 @@ describe("formatCheckLib", () => {
]
}
const firebaseAppId = '1:1233455666:android:abcdefgh'
const api_secret = '123'

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors).toEqual([])
})

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

let errors = formatCheckLib(payload, firebaseAppId)
let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors[0].description).toEqual(
`${firebaseAppId} does not follow firebase_app_id pattern of X:XX:XX:XX at path`
)
})
})

describe("validates api_secret", () => {
test("does not return an error api_secret is not null", () => {
const payload = {}
const firebaseAppId = '1:1233455666:android:abcdefgh'
const api_secret = '123'

let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors).toEqual([])
})

test("returns an error when api_secret is null", () => {
const payload = {}
const firebaseAppId = '1:1233455666:android:abcdefgh'
const api_secret = ''

let errors = formatCheckLib(payload, firebaseAppId, api_secret)

expect(errors[0].description).toEqual(
"Unable to find non-empty parameter [api_secret] value in request."
)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const RESERVED_USER_PROPERTY_NAMES = [
]

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

const appInstanceIdErrors = isValidAppInstanceId(payload)
Expand All @@ -28,6 +28,7 @@ export const formatCheckLib = (payload, firebaseAppId) => {
const emptyItemsErrors = isItemsEmpty(payload)
const itemsRequiredKeyErrors = itemsHaveRequiredKey(payload)
const firebaseAppIdErrors = isfirebaseAppIdValid(firebaseAppId)
const apiSecretErrors = isApiSecretNotNull(api_secret)
const sizeErrors = isTooBig(payload)

return [
Expand All @@ -39,6 +40,7 @@ export const formatCheckLib = (payload, firebaseAppId) => {
...emptyItemsErrors,
...itemsRequiredKeyErrors,
...firebaseAppIdErrors,
...apiSecretErrors,
...sizeErrors,
]
}
Expand Down Expand Up @@ -183,7 +185,7 @@ const requiredKeysEmpty = (itemsObj) => {
const isfirebaseAppIdValid = (firebaseAppId) => {
let errors: ValidationMessage[] = []

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

const isApiSecretNotNull = (api_secret) => {
let errors: ValidationMessage[] = []

if (!api_secret) {
errors.push({
description: "Unable to find non-empty parameter [api_secret] value in request.",
validationCode: "VALUE_REQUIRED",
fieldPath: "api_secret"
})
}

return errors
}

const isTooBig = (payload) => {
let errors: ValidationMessage[] = []

Expand Down
Loading

0 comments on commit c8da06f

Please sign in to comment.