Skip to content

Commit

Permalink
Add headers to webhook alerts (#320)
Browse files Browse the repository at this point in the history
This PR allows the user to customize the headers field for their webhook
alerts in addition to the payload. This will enable auth, which is
required by a lot of different platforms.

---------

Co-authored-by: Auto-format Bot <[email protected]>
  • Loading branch information
f-wright and Auto-format Bot authored Feb 25, 2025
1 parent 401bd14 commit f5c7f6a
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 6 deletions.
2 changes: 0 additions & 2 deletions generated/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,4 @@ setup.cfg
setup.py
test-requirements.txt
test/__init__.py
test/test_payload_template.py
test/test_payload_template_request.py
tox.ini
1 change: 1 addition & 0 deletions generated/docs/PayloadTemplate.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**template** | **str** | |
**headers** | **{str: (str,)}, none_type** | | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
Expand Down
1 change: 1 addition & 0 deletions generated/docs/PayloadTemplateRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**template** | **str** | |
**headers** | **{str: (str,)}, none_type** | | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ def openapi_types():
"""
return {
"template": (str,), # noqa: E501
"headers": (
{str: (str,)},
none_type,
), # noqa: E501
}

@cached_property
Expand All @@ -97,6 +101,7 @@ def discriminator():

attribute_map = {
"template": "template", # noqa: E501
"headers": "headers", # noqa: E501
}

read_only_vars = {}
Expand Down Expand Up @@ -142,6 +147,7 @@ def _from_openapi_data(cls, template, *args, **kwargs): # noqa: E501
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
headers ({str: (str,)}, none_type): [optional] # noqa: E501
"""

_check_type = kwargs.pop("_check_type", True)
Expand Down Expand Up @@ -230,6 +236,7 @@ def __init__(self, template, *args, **kwargs): # noqa: E501
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
headers ({str: (str,)}, none_type): [optional] # noqa: E501
"""

_check_type = kwargs.pop("_check_type", True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ def openapi_types():
"""
return {
"template": (str,), # noqa: E501
"headers": (
{str: (str,)},
none_type,
), # noqa: E501
}

@cached_property
Expand All @@ -101,6 +105,7 @@ def discriminator():

attribute_map = {
"template": "template", # noqa: E501
"headers": "headers", # noqa: E501
}

read_only_vars = {}
Expand Down Expand Up @@ -146,6 +151,7 @@ def _from_openapi_data(cls, template, *args, **kwargs): # noqa: E501
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
headers ({str: (str,)}, none_type): [optional] # noqa: E501
"""

_check_type = kwargs.pop("_check_type", True)
Expand Down Expand Up @@ -234,6 +240,7 @@ def __init__(self, template, *args, **kwargs): # noqa: E501
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
headers ({str: (str,)}, none_type): [optional] # noqa: E501
"""

_check_type = kwargs.pop("_check_type", True)
Expand Down
4 changes: 3 additions & 1 deletion generated/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: public-api.yaml
# timestamp: 2025-02-24T22:54:15+00:00
# timestamp: 2025-02-25T19:28:25+00:00

from __future__ import annotations

Expand Down Expand Up @@ -111,10 +111,12 @@ class NoteRequest(BaseModel):

class PayloadTemplate(BaseModel):
template: str
headers: Optional[Dict[str, str]] = None


class PayloadTemplateRequest(BaseModel):
template: constr(min_length=1)
headers: Optional[Dict[str, constr(min_length=1)]] = None


class ROI(BaseModel):
Expand Down
11 changes: 11 additions & 0 deletions spec/public-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,11 @@ components:
properties:
template:
type: string
headers:
type: object
additionalProperties:
type: string
nullable: true
required:
- template
PayloadTemplateRequest:
Expand All @@ -1351,6 +1356,12 @@ components:
template:
type: string
minLength: 1
headers:
type: object
additionalProperties:
type: string
minLength: 1
nullable: true
required:
- template
ROI:
Expand Down
9 changes: 6 additions & 3 deletions src/groundlight/experimental_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,11 @@ def make_webhook_action(
payload_template=payload_template,
)

def make_payload_template(self, template: str) -> PayloadTemplate:
def make_payload_template(self, template: str, headers: Optional[Dict[str, str]] = None) -> PayloadTemplate:
"""
Creates a PayloadTemplate object for use in creating alerts
"""
return PayloadTemplate(template=template)
return PayloadTemplate(template=template, headers=headers)

def create_alert( # pylint: disable=too-many-locals, too-many-arguments # noqa: PLR0913
self,
Expand Down Expand Up @@ -282,7 +282,10 @@ def create_alert( # pylint: disable=too-many-locals, too-many-arguments # noqa
url=str(webhook_action.url),
include_image=webhook_action.include_image,
payload_template=(
PayloadTemplateRequest(template=webhook_action.payload_template.template)
PayloadTemplateRequest(
template=webhook_action.payload_template.template,
headers=webhook_action.payload_template.headers,
)
if webhook_action.payload_template
else None
),
Expand Down
39 changes: 39 additions & 0 deletions test/unit/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,42 @@ def test_create_alert_webhook_action_with_invalid_payload_template(gl_experiment
with pytest.raises(ApiException) as e:
gl_experimental.create_alert(det, f"test_alert_{name}", condition, webhook_actions=webhook_action)
assert e.value.status == bad_request_exception_status_code


def test_create_alert_webhook_action_headers(gl_experimental: ExperimentalApi):
name = f"Test {datetime.utcnow()}"
det = gl_experimental.get_or_create_detector(name, "test_query")
condition = gl_experimental.make_condition("ANSWERED_CONSECUTIVELY", {"num_consecutive_labels": 1, "label": "YES"})

test_api_key = "test_api_key"
url = "https://example.com/webhook"
headers = {
"Authorization": f"Bearer {test_api_key}",
}

template = """{"records": [{"fields": {"detector_id": "{{ detector_id }}" } }]}"""

payload_template = {"template": template, "headers": headers}
webhook_action = gl_experimental.make_webhook_action(
url=url, include_image=False, payload_template=payload_template
)

alert = gl_experimental.create_alert(
det,
f"test_alert_{name}",
condition,
webhook_actions=webhook_action,
)

assert len(alert.webhook_action) == 1
assert alert.webhook_action[0].payload_template.template == template
assert alert.webhook_action[0].payload_template.headers == headers


def test_create_invalid_payload_template_headers(gl_experimental: ExperimentalApi):
with pytest.raises(Exception) as e:
gl_experimental.make_payload_template(
'{"template": "This is a fine template"}', headers="bad headers" # type: ignore
)
assert e.typename == "ValidationError"
assert "Input should be a valid dictionary" in str(e.value)

0 comments on commit f5c7f6a

Please sign in to comment.