Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
EditorRef,
EmailTemplate,
EmailTemplaterLogicProps,
buildPersonPropertyMergeValue,
emailTemplaterLogic,
} from './emailTemplaterLogic'

Expand Down Expand Up @@ -622,3 +623,18 @@ describe('emailTemplaterLogic', () => {
})
})
})

describe('buildPersonPropertyMergeValue', () => {
// Never emit double quotes: they end an HTML attribute like a link href, and entity-encoded they
// render an empty value. A bare identifier uses dot access; everything else uses single-quoted
// brackets.
it.each([
['first_name', '{{person.properties.first_name}}'],
['$browser', "{{person.properties['$browser']}}"],
['renews on', "{{person.properties['renews on']}}"],
['a.b', "{{person.properties['a.b']}}"],
["it's", "{{person.properties['it\\'s']}}"],
])('builds %s as %s', (name, expected) => {
expect(buildPersonPropertyMergeValue(name)).toBe(expected)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ export function buildHtmlWrapDesign(html: string): JSONTemplate {
const EMAIL_EDITOR_URL_PARAM = 'editor'
const EMAIL_EDITOR_URL_VALUE = 'email'

// A property name reads back as `person.properties.foo` only when it is a bare identifier. Anything
// else (spaces, a leading $, punctuation) needs bracket access. Use single quotes, never double: a
// double quote inside an HTML attribute like a link href ends the attribute and breaks the tag, and
// when it survives as an HTML entity the renderer resolves the tag to an empty string. This mirrors
// buildDelayExpression in products/workflows stepDelayLogic.
const BARE_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/

export function buildPersonPropertyMergeValue(name: string): string {
if (BARE_IDENTIFIER_REGEX.test(name)) {
return `{{person.properties.${name}}}`
}
const escaped = name.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
return `{{person.properties['${escaped}']}}`
}

export interface EmailTemplaterLogicProps {
value: EmailTemplate | null
onChange: (value: EmailTemplate) => void
Expand Down Expand Up @@ -499,7 +514,7 @@ export const emailTemplaterLogic = kea<emailTemplaterLogicType>([
personPropertyDefinitions.forEach((property: PropertyDefinition) => {
tags[property.name] = {
name: property.name,
value: `{{person.properties["${property.name}"]}}`,
value: buildPersonPropertyMergeValue(property.name),
sample: property.example || `Sample ${property.name}`,
}
})
Expand Down
Loading