From 58a96cdec2c4aff5435d287ff60a9be433ca872d Mon Sep 17 00:00:00 2001 From: Marcus Hof <13001502+MarconLP@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:13:10 +0200 Subject: [PATCH] feat(cdp): add loops hog function (#24713) --- frontend/public/services/loops.png | Bin 0 -> 13692 bytes posthog/cdp/templates/__init__.py | 2 + posthog/cdp/templates/loops/template_loops.py | 49 ++++++++++++++ .../templates/loops/test_template_loops.py | 61 ++++++++++++++++++ .../test/__snapshots__/test_in_cohort.ambr | 8 +-- 5 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 frontend/public/services/loops.png create mode 100644 posthog/cdp/templates/loops/template_loops.py create mode 100644 posthog/cdp/templates/loops/test_template_loops.py diff --git a/frontend/public/services/loops.png b/frontend/public/services/loops.png new file mode 100644 index 0000000000000000000000000000000000000000..822763a10e1081d0ec3ec20087779281b32e5333 GIT binary patch literal 13692 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 literal 0 HcmV?d00001 diff --git a/posthog/cdp/templates/__init__.py b/posthog/cdp/templates/__init__.py index 32725b5a57367..f1f0243c08cd5 100644 --- a/posthog/cdp/templates/__init__.py +++ b/posthog/cdp/templates/__init__.py @@ -14,6 +14,7 @@ ) from .zapier.template_zapier import template as zapier from .mailgun.template_mailgun import template_mailgun_send_email as mailgun +from .loops.template_loops import template as loops from .rudderstack.template_rudderstack import template as rudderstack @@ -33,6 +34,7 @@ mailjet_update_contact_list, clearbit, mailgun, + loops, rudderstack, ] diff --git a/posthog/cdp/templates/loops/template_loops.py b/posthog/cdp/templates/loops/template_loops.py new file mode 100644 index 0000000000000..01230f7727328 --- /dev/null +++ b/posthog/cdp/templates/loops/template_loops.py @@ -0,0 +1,49 @@ +from posthog.cdp.templates.hog_function_template import HogFunctionTemplate + + +template: HogFunctionTemplate = HogFunctionTemplate( + status="beta", + id="template-loops", + name="Send events to Loops", + description="Passes PostHog events to Loops.so", + icon_url="/static/services/loops.png", + hog=""" +let apiKey := inputs.apiKey + +let payload := { + 'userId': event.distinct_id, + 'eventName': event.name == '$set' ? '$identify' : event.name, + 'email': person.properties.email +} +for (let key, value in person.properties) { + payload[key] := value +} +fetch('https://app.loops.so/api/v1/events/send', { + 'method': 'POST', + 'headers': { + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {apiKey}', + }, + 'body': payload +}) +""".strip(), + inputs_schema=[ + { + "key": "apiKey", + "type": "string", + "label": "Loops API Key", + "description": "Loops API Key", + "default": "", + "secret": True, + "required": True, + } + ], + 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/loops/test_template_loops.py b/posthog/cdp/templates/loops/test_template_loops.py new file mode 100644 index 0000000000000..c6d48b5228b14 --- /dev/null +++ b/posthog/cdp/templates/loops/test_template_loops.py @@ -0,0 +1,61 @@ +from inline_snapshot import snapshot +from posthog.cdp.templates.helpers import BaseHogFunctionTemplateTest +from posthog.cdp.templates.loops.template_loops import template as template_loops + + +class TestTemplateLoops(BaseHogFunctionTemplateTest): + template = template_loops + + def _inputs(self, **kwargs): + inputs = {"apiKey": "1cac089e00a708680bdb1ed9f082d5bf"} + inputs.update(kwargs) + return inputs + + def test_function_works(self): + self.run_function( + inputs=self._inputs(), + globals={ + "event": {"distinct_id": "66e614bd-d9f2-491e-9e2c-eeab3090f72f", "name": "$pageview"}, + "person": { + "properties": {"email": "max@posthog.com", "name": "Max", "company": "PostHog"}, + }, + }, + ) + + assert self.get_mock_fetch_calls()[0] == snapshot( + ( + "https://app.loops.so/api/v1/events/send", + { + "method": "POST", + "headers": { + "Content-Type": "application/json", + "Authorization": "Bearer 1cac089e00a708680bdb1ed9f082d5bf", + }, + "body": { + "userId": "66e614bd-d9f2-491e-9e2c-eeab3090f72f", + "eventName": "$pageview", + "email": "max@posthog.com", + "name": "Max", + "company": "PostHog", + }, + }, + ) + ) + + def test_automatic_action_mapping(self): + for event_name, expected_action in [ + ("$identify", "$identify"), + ("$set", "$identify"), + ("$pageview", "$pageview"), + ("$create_alias", "$create_alias"), + ("$autocapture", "$autocapture"), + ("custom", "custom"), + ]: + self.run_function( + inputs=self._inputs(), + globals={ + "event": {"name": event_name, "properties": {"url": "https://example.com", "$browser": "Chrome"}}, + }, + ) + + assert self.get_mock_fetch_calls()[0][1]["body"]["eventName"] == expected_action diff --git a/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr b/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr index 66203f817267f..369f18ab9d118 100644 --- a/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr +++ b/posthog/hogql/transforms/test/__snapshots__/test_in_cohort.ambr @@ -31,7 +31,7 @@ FROM events LEFT JOIN ( SELECT person_static_cohort.person_id AS cohort_person_id, 1 AS matched, person_static_cohort.cohort_id AS cohort_id FROM person_static_cohort - WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [13]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id) + WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [2]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id) WHERE and(equals(events.team_id, 420), 1, ifNull(equals(__in_cohort.matched, 1), 0)) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, max_ast_elements=4000000, max_expanded_ast_elements=4000000, max_bytes_before_external_group_by=0 @@ -42,7 +42,7 @@ FROM events LEFT JOIN ( SELECT person_id AS cohort_person_id, 1 AS matched, cohort_id FROM static_cohort_people - WHERE in(cohort_id, [13])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id) + WHERE in(cohort_id, [2])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id) WHERE and(1, equals(__in_cohort.matched, 1)) LIMIT 100 ''' @@ -55,7 +55,7 @@ FROM events LEFT JOIN ( SELECT person_static_cohort.person_id AS cohort_person_id, 1 AS matched, person_static_cohort.cohort_id AS cohort_id FROM person_static_cohort - WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [14]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id) + WHERE and(equals(person_static_cohort.team_id, 420), in(person_static_cohort.cohort_id, [3]))) AS __in_cohort ON equals(__in_cohort.cohort_person_id, events.person_id) WHERE and(equals(events.team_id, 420), 1, ifNull(equals(__in_cohort.matched, 1), 0)) LIMIT 100 SETTINGS readonly=2, max_execution_time=60, allow_experimental_object_type=1, format_csv_allow_double_quotes=0, max_ast_elements=4000000, max_expanded_ast_elements=4000000, max_bytes_before_external_group_by=0 @@ -66,7 +66,7 @@ FROM events LEFT JOIN ( SELECT person_id AS cohort_person_id, 1 AS matched, cohort_id FROM static_cohort_people - WHERE in(cohort_id, [14])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id) + WHERE in(cohort_id, [3])) AS __in_cohort ON equals(__in_cohort.cohort_person_id, person_id) WHERE and(1, equals(__in_cohort.matched, 1)) LIMIT 100 '''