Skip to content

Commit

Permalink
fix: Hog function template defaults (#27508)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite authored Jan 15, 2025
1 parent b174f22 commit 5ba943f
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 23 deletions.
15 changes: 15 additions & 0 deletions ee/api/test/test_hooks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import cast
from unittest.mock import ANY

from inline_snapshot import snapshot

from ee.api.hooks import valid_domain
from ee.api.test.base import APILicensedTest
from ee.models.hook import Hook
Expand Down Expand Up @@ -117,6 +119,19 @@ def test_create_hog_function_via_hook(self):
"bytecode": ["_H", HOGQL_BYTECODE_VERSION, 32, "$pageview", 32, "event", 1, 1, 11, 3, 1, 4, 1],
}

assert hog_function.hog == snapshot(
"""\
let res := fetch(f'https://hooks.zapier.com/{inputs.hook}', {
'method': 'POST',
'body': inputs.body
});
if (inputs.debug) {
print('Response', res.status, res.body);
}\
"""
)

assert hog_function.inputs == {
"body": {
"bytecode": ANY,
Expand Down
24 changes: 17 additions & 7 deletions posthog/api/hog_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,12 @@ def validate(self, attrs):
instance = cast(Optional[HogFunction], self.context.get("instance", self.instance))

hog_type = attrs.get("type", instance.type if instance else "destination")
is_create = self.context.get("view") and self.context["view"].action == "create"

if not has_addon:
template_id = attrs.get("template_id", instance.template_id if instance else None)
template = HOG_FUNCTION_TEMPLATES_BY_ID.get(template_id, None)
template_id = attrs.get("template_id", instance.template_id if instance else None)
template = HOG_FUNCTION_TEMPLATES_BY_ID.get(template_id, None)

if not has_addon:
# In this case they are only allowed to create or update the function with free templates
if not template:
raise serializers.ValidationError(
Expand All @@ -168,18 +169,23 @@ def validate(self, attrs):
{"template_id": "The Data Pipelines addon is required for this template."}
)

# Without the addon, they cannot deviate from the template
attrs["inputs_schema"] = template.inputs_schema
attrs["mappings"] = template.mappings
# Without the addon you can't deviate from the template
attrs["hog"] = template.hog
attrs["inputs_schema"] = template.inputs_schema

if self.context.get("view") and self.context["view"].action == "create":
if is_create:
# Ensure we have sensible defaults when created
attrs["filters"] = attrs.get("filters") or {}
attrs["inputs_schema"] = attrs.get("inputs_schema") or []
attrs["inputs"] = attrs.get("inputs") or {}
attrs["mappings"] = attrs.get("mappings") or None

# And if there is a template, use the template values if not overridden
if template:
attrs["hog"] = attrs.get("hog") or template.hog
attrs["inputs_schema"] = attrs.get("inputs_schema") or template.inputs_schema
attrs["inputs"] = attrs.get("inputs") or {}

# Used for both top level input validation, and mappings input validation
def validate_input_and_filters(attrs: dict):
if "inputs_schema" in attrs:
Expand Down Expand Up @@ -235,6 +241,10 @@ def validate_input_and_filters(attrs: dict):
attrs["bytecode"] = None
attrs["transpiled"] = None

if is_create:
if not attrs.get("hog"):
raise serializers.ValidationError({"hog": "Required."})

return super().validate(attrs)

def to_representation(self, data):
Expand Down
50 changes: 34 additions & 16 deletions posthog/api/test/test_hog_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,14 @@ def test_creates_with_template_id(self, *args):
"name": "Fetch URL",
"description": "Test description",
"hog": "fetch(inputs.url);",
"inputs": {"url": {"value": "https://example.com"}},
"template_id": template_webhook.id,
"type": "destination",
},
)
assert response.status_code == status.HTTP_201_CREATED, response.json()

assert response.json()["hog"] == "fetch(inputs.url);"
assert response.json()["template"] == {
"type": "destination",
"name": template_webhook.name,
Expand All @@ -286,14 +289,35 @@ def test_creates_with_template_id(self, *args):
"sub_templates": response.json()["template"]["sub_templates"],
}

def test_creates_with_template_values_if_not_provided(self, *args):
payload: dict = {
"name": "Fetch URL",
"description": "Test description",
"template_id": template_webhook.id,
"type": "destination",
}
response = self.client.post(f"/api/projects/{self.team.id}/hog_functions/", data=payload)
assert response.status_code == status.HTTP_400_BAD_REQUEST, response.json()
assert response.json() == {
"attr": "inputs__url",
"code": "invalid_input",
"detail": "This field is required.",
"type": "validation_error",
}

payload["inputs"] = {"url": {"value": "https://example.com"}}

response = self.client.post(f"/api/projects/{self.team.id}/hog_functions/", data=payload)
assert response.status_code == status.HTTP_201_CREATED, response.json()
assert response.json()["hog"] == template_webhook.hog
assert response.json()["inputs_schema"] == template_webhook.inputs_schema

def test_deletes_via_update(self, *args):
response = self.client.post(
f"/api/projects/{self.team.id}/hog_functions/",
data={
"type": "destination",
**EXAMPLE_FULL,
"name": "Fetch URL",
"description": "Test description",
"hog": "fetch(inputs.url);",
},
)
assert response.status_code == status.HTTP_201_CREATED, response.json()
Expand Down Expand Up @@ -640,8 +664,7 @@ def test_generates_hog_bytecode(self, *args):
response = self.client.post(
f"/api/projects/{self.team.id}/hog_functions/",
data={
"type": "destination",
"name": "Fetch URL",
**EXAMPLE_FULL,
"hog": "let i := 0;\nwhile(i < 3) {\n i := i + 1;\n fetch(inputs.url, {\n 'headers': {\n 'x-count': f'{i}'\n },\n 'body': inputs.payload,\n 'method': inputs.method\n });\n}",
},
)
Expand Down Expand Up @@ -882,7 +905,10 @@ def test_patches_status_on_enabled_update(self, *args):

response = self.client.post(
f"/api/projects/{self.team.id}/hog_functions/",
data={"type": "destination", "name": "Fetch URL", "hog": "fetch(inputs.url);", "enabled": True},
data={
**EXAMPLE_FULL,
"name": "Fetch URL",
},
)
id = response.json()["id"]

Expand Down Expand Up @@ -1110,11 +1136,7 @@ def test_create_hog_function_with_site_destination_type(self):
def test_cannot_modify_type_of_existing_hog_function(self):
response = self.client.post(
f"/api/projects/{self.team.id}/hog_functions/",
data={
"name": "Site Destination Function",
"hog": "export function onLoad() { console.log('Hello, site_destination'); }",
"type": "site_destination",
},
data=EXAMPLE_FULL,
)

assert response.status_code == status.HTTP_201_CREATED, response.json()
Expand All @@ -1134,11 +1156,7 @@ def test_cannot_modify_type_of_existing_hog_function(self):
def test_transpiled_field_not_populated_for_other_types(self):
response = self.client.post(
f"/api/projects/{self.team.id}/hog_functions/",
data={
"name": "Regular Function",
"hog": "fetch(inputs.url);",
"type": "destination",
},
data=EXAMPLE_FULL,
)

assert response.status_code == status.HTTP_201_CREATED, response.json()
Expand Down

0 comments on commit 5ba943f

Please sign in to comment.