Add is_legacy column to handle webhook migration (#1813)

Legacy webhooks won't be editable at first. Keep data templates
compatibility.

Possible migration code:
```python
from apps.webhooks.models import Webhook
from apps.alerts.models import CustomButton, EscalationPolicy

custom_buttons = CustomButton.objects.all()
for cb in custom_buttons:
    webhook, _ = Webhook.objects.get_or_create(
        organization=cb.organization,
        team=cb.team,
        name=cb.name,
        is_legacy=True,
        defaults=dict(
            created_at=cb.created_at,
            url=cb.webhook,
            username=cb.user,
            password=cb.password,
            authorization_header=cb.authorization_header,
            trigger_type=Webhook.TRIGGER_ESCALATION_STEP,
            forward_all=cb.forward_whole_payload,
            data=cb.data,
        )
    )
    # migrate related escalation policies
    policies = EscalationPolicy.objects.filter(
        step=EscalationPolicy.STEP_TRIGGER_CUSTOM_BUTTON,
        custom_button_trigger=cb,
    ).update(
        step=EscalationPolicy.STEP_TRIGGER_CUSTOM_WEBHOOK,
        custom_webhook=webhook,
    )

```
This commit is contained in:
Matias Bordese 2023-04-25 11:22:56 -03:00 committed by GitHub
parent 0a6d513693
commit 20ec6f52bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 72 additions and 3 deletions

View file

@ -40,6 +40,7 @@ class WebhookSerializer(serializers.ModelSerializer):
"id",
"name",
"is_webhook_enabled",
"is_legacy",
"team",
"data",
"user",

View file

@ -51,6 +51,7 @@ def test_get_list_webhooks(webhook_internal_api_setup, make_user_auth_headers):
"http_method": "POST",
"integration_filter": None,
"is_webhook_enabled": True,
"is_legacy": False,
"last_response_log": {
"request_data": "",
"request_headers": "",
@ -91,6 +92,7 @@ def test_get_detail_webhook(webhook_internal_api_setup, make_user_auth_headers):
"http_method": "POST",
"integration_filter": None,
"is_webhook_enabled": True,
"is_legacy": False,
"last_response_log": {
"request_data": "",
"request_headers": "",
@ -136,6 +138,7 @@ def test_create_webhook(mocked_check_webhooks_2_enabled, webhook_internal_api_se
"http_method": "POST",
"integration_filter": None,
"is_webhook_enabled": True,
"is_legacy": False,
"last_response_log": {
"request_data": "",
"request_headers": "",
@ -194,6 +197,7 @@ def test_create_valid_templated_field(
"http_method": "POST",
"integration_filter": None,
"is_webhook_enabled": True,
"is_legacy": False,
"last_response_log": {
"request_data": "",
"request_headers": "",

View file

@ -0,0 +1,18 @@
# Generated by Django 3.2.18 on 2023-04-24 14:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webhooks', '0004_auto_20230418_0109'),
]
operations = [
migrations.AddField(
model_name='webhook',
name='is_legacy',
field=models.BooleanField(default=False, null=True),
),
]

View file

@ -112,6 +112,7 @@ class Webhook(models.Model):
trigger_type = models.IntegerField(choices=TRIGGER_TYPES, default=None, null=True)
is_webhook_enabled = models.BooleanField(null=True, default=True)
integration_filter = models.JSONField(default=None, null=True, blank=True)
is_legacy = models.BooleanField(null=True, default=False)
class Meta:
unique_together = ("name", "organization")
@ -156,11 +157,19 @@ class Webhook(models.Model):
if self.http_method in ["POST", "PUT"]:
if self.forward_all:
request_kwargs["json"] = event_data
if self.is_legacy:
request_kwargs["json"] = event_data["alert_payload"]
elif self.data:
context_data = event_data
if self.is_legacy:
context_data = {
"alert_payload": event_data.get("alert_payload", {}),
"alert_group_id": event_data.get("alert_group_id"),
}
try:
rendered_data = apply_jinja_template_for_json(
self.data,
event_data,
context_data,
)
try:
request_kwargs["json"] = json.loads(rendered_data)

View file

@ -104,6 +104,35 @@ def test_build_request_kwargs_custom_data(make_organization, make_custom_webhook
assert request_kwargs == {"headers": {}, "data": "bar"}
@pytest.mark.django_db
def test_build_request_kwargs_is_legacy_custom_data(make_organization, make_custom_webhook):
organization = make_organization()
webhook = make_custom_webhook(
organization=organization,
data="{{alert_payload.message}}",
forward_all=False,
is_legacy=True,
)
event_data = {"alert_group_id": "bar", "alert_payload": {"message": "the-message"}}
request_kwargs = webhook.build_request_kwargs(event_data)
assert request_kwargs == {"headers": {}, "data": "the-message"}
@pytest.mark.django_db
def test_build_request_kwargs_is_legacy_forward_all(make_organization, make_custom_webhook):
organization = make_organization()
webhook = make_custom_webhook(
organization=organization,
forward_all=True,
is_legacy=True,
)
event_data = {"alert_group_id": "bar", "alert_payload": {"message": "the-message"}}
request_kwargs = webhook.build_request_kwargs(event_data)
assert request_kwargs == {"headers": {}, "json": event_data["alert_payload"]}
@pytest.mark.django_db
def test_build_request_kwargs_custom_data_error(make_organization, make_custom_webhook):
organization = make_organization()

View file

@ -30,7 +30,7 @@ const OutgoingWebhook2Form = observer((props: OutgoingWebhook2FormProps) => {
const { outgoingWebhook2Store } = store;
const data = id === 'new' ? { is_webhook_enabled: true } : outgoingWebhook2Store.items[id];
const data = id === 'new' ? { is_webhook_enabled: true, is_legacy: false } : outgoingWebhook2Store.items[id];
const handleSubmit = useCallback(
(data: Partial<OutgoingWebhook2>) => {
@ -57,11 +57,18 @@ const OutgoingWebhook2Form = observer((props: OutgoingWebhook2FormProps) => {
<div className={cx('content')} data-testid="test__outgoingWebhook2EditForm">
<GForm form={form} data={data} onSubmit={handleSubmit} />
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
<Button form={form.name} type="submit">
<Button form={form.name} type="submit" disabled={data.is_legacy}>
{id === 'new' ? 'Create' : 'Update'} Webhook
</Button>
</WithPermissionControlTooltip>
</div>
{data.is_legacy ? (
<div className={cx('content')}>
<Text type="secondary">Legacy migrated webhooks are not editable.</Text>
</div>
) : (
''
)}
</Drawer>
);
});

View file

@ -17,6 +17,7 @@ export interface OutgoingWebhook2 {
trigger_template: string;
last_response_log?: OutgoingWebhook2Response;
is_webhook_enabled: boolean;
is_legacy: boolean;
}
export interface OutgoingWebhook2Response {