From 2e130994ac05a1697bdfee904c86045dfc10c1d2 Mon Sep 17 00:00:00 2001 From: Marcus Hof <13001502+MarconLP@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:04:49 +0200 Subject: [PATCH] feat(cdp): add mailchimp template (#25781) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- frontend/public/services/mailchimp.png | Bin 0 -> 9091 bytes posthog/cdp/templates/__init__.py | 2 + .../templates/mailchimp/template_mailchimp.py | 135 ++++++++++++++++++ .../mailchimp/test_template_mailchimp.py | 92 ++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 frontend/public/services/mailchimp.png create mode 100644 posthog/cdp/templates/mailchimp/template_mailchimp.py create mode 100644 posthog/cdp/templates/mailchimp/test_template_mailchimp.py diff --git a/frontend/public/services/mailchimp.png b/frontend/public/services/mailchimp.png new file mode 100644 index 0000000000000000000000000000000000000000..d57818566818bb56c2d88e947421204f1997d70b GIT binary patch literal 9091 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 400) { + throw Error(f'Error from api.mailchimp.com (status {userStatus.status}): {userStatus.body}') + } +} else if (userStatus.status >= 400) { + throw Error(f'Error from api.mailchimp.com (status {userStatus.status}): {userStatus.body}') +} + +""".strip(), + inputs_schema=[ + { + "key": "apiKey", + "type": "string", + "label": "Mailchimp API Key", + "description": "See the docs here: https://mailchimp.com/help/about-api-keys/", + "secret": True, + "required": True, + }, + { + "key": "audienceId", + "type": "string", + "label": "Mailchimp audience ID", + "description": "See the docs here: https://mailchimp.com/help/find-audience-id/", + "secret": False, + "required": True, + }, + { + "key": "dataCenterId", + "type": "string", + "label": "Mailchimp data center ID", + "description": "You can find your Datacenter ID in the Mailchimp url in your browser when you're logged in. It's the 'us1' in 'https://us1.admin.mailchimp.com/lists/'", + "secret": False, + "required": True, + }, + { + "key": "email", + "type": "string", + "label": "Email of the user", + "description": "Where to find the email for the contact to be created. You can use the filters section to filter out unwanted emails or internal users.", + "default": "{person.properties.email}", + "secret": False, + "required": True, + }, + { + "key": "doubleOptIn", + "type": "boolean", + "label": "Enable double opt-in", + "description": "If enabled, Mailchimp sends a confirmation email to that user, and that email is tagged with a pending subscriber status. The subscriber status automatically changes to subscribed once the user confirms the email.", + "default": False, + "secret": False, + "required": True, + }, + { + "key": "include_all_properties", + "type": "boolean", + "label": "Include all event properties", + "description": "If set, all person properties will be included. Individual properties can be overridden below.", + "default": False, + "secret": False, + "required": True, + }, + { + "key": "properties", + "type": "dictionary", + "label": "Merge field", + "description": "Map of Mailchimp merge fields and their values. You can use the filters section to filter out unwanted events. Check out this page for more details: https://mailchimp.com/developer/marketing/docs/merge-fields/#add-merge-data-to-contacts", + "default": { + "FNAME": "{person.properties.firstname}", + "LNAME": "{person.properties.lastname}", + "COMPANY": "{person.properties.company}", + }, + "secret": False, + "required": False, + }, + ], + filters={ + "events": [ + {"id": "$identify", "name": "$identify", "type": "events", "order": 0}, + {"id": "$set", "name": "$set", "type": "events", "order": 1}, + ], + "actions": [], + "filter_test_accounts": True, + }, +) diff --git a/posthog/cdp/templates/mailchimp/test_template_mailchimp.py b/posthog/cdp/templates/mailchimp/test_template_mailchimp.py new file mode 100644 index 0000000000000..ebfd776b0252d --- /dev/null +++ b/posthog/cdp/templates/mailchimp/test_template_mailchimp.py @@ -0,0 +1,92 @@ +from inline_snapshot import snapshot +from posthog.cdp.templates.helpers import BaseHogFunctionTemplateTest +from posthog.cdp.templates.mailchimp.template_mailchimp import ( + template as template_mailchimp, +) + + +def create_inputs(**kwargs): + inputs = { + "apiKey": "abcdef", + "audienceId": "a1b2c3", + "dataCenterId": "us1", + "email": "max@posthog.com", + "include_all_properties": False, + "doubleOptIn": False, + "properties": {"FNAME": "Max", "LNAME": "AI", "COMPANY": "PostHog"}, + } + inputs.update(kwargs) + + return inputs + + +class TestTemplateMailchimp(BaseHogFunctionTemplateTest): + template = template_mailchimp + + def test_function_works(self): + self.run_function( + inputs=create_inputs(), + globals={ + "event": {"event": "$identify"}, + }, + ) + + assert self.get_mock_fetch_calls()[0] == snapshot( + ( + "https://us1.api.mailchimp.com/3.0/lists/a1b2c3/members/12d91149f17f7ac265e833ea05ef6249", + { + "method": "GET", + "headers": { + "Authorization": "Bearer abcdef", + "Content-Type": "application/json", + }, + }, + ) + ) + + def test_body_includes_all_properties_if_set(self): + self.run_function( + inputs=create_inputs(include_all_properties=False), + globals={ + "event": {"properties": {"PHONE": "+1415000000"}}, + }, + ) + + assert self.get_mock_fetch_calls()[1][1]["body"]["merge_fields"] == snapshot( + {"FNAME": "Max", "LNAME": "AI", "COMPANY": "PostHog"} + ) + + self.run_function( + inputs=create_inputs(include_all_properties=True), + globals={ + "event": {"properties": {"PHONE": "+1415000000"}}, + }, + ) + + assert self.get_mock_fetch_calls()[1][1]["body"]["merge_fields"] == snapshot( + { + "FNAME": "Max", + "LNAME": "AI", + "COMPANY": "PostHog", + "PHONE": "+1415000000", + } + ) + + def test_double_opt_in(self): + self.run_function( + inputs=create_inputs(doubleOptIn=False), + ) + + assert self.get_mock_fetch_calls()[1][1]["body"]["status_if_new"] == snapshot("subscribed") + + self.run_function( + inputs=create_inputs(doubleOptIn=True), + ) + + assert self.get_mock_fetch_calls()[1][1]["body"]["status_if_new"] == snapshot("pending") + + def test_function_requires_identifier(self): + self.run_function(inputs=create_inputs(email="")) + + assert not self.get_mock_fetch_calls() + assert self.get_mock_print_calls() == snapshot([("No email set. Skipping...",)])